@@ -199,6 +199,12 @@ def _lcc_params(crs):
199199 if d .get ('proj' ) != 'lcc' :
200200 return None
201201
202+ units = d .get ('units' , 'm' )
203+ _UNIT_TO_METER = {'m' : 1.0 , 'us-ft' : 0.3048006096012192 , 'ft' : 0.3048 }
204+ to_meter = _UNIT_TO_METER .get (units )
205+ if to_meter is None :
206+ return None
207+
202208 lat_1 = math .radians (d .get ('lat_1' , d .get ('lat_0' , 0.0 )))
203209 lat_2 = math .radians (d .get ('lat_2' , lat_1 ))
204210 lat_0 = math .radians (d .get ('lat_0' , 0.0 ))
@@ -231,10 +237,10 @@ def _lcc_params(crs):
231237 (1.0 + e * sinphi0 ) / (1.0 - e * sinphi0 ), e / 2.0 )
232238 rho0 = a * k0_param * c * math .pow (ts0 , n )
233239
234- fe = d .get ('x_0' , 0.0 )
240+ fe = d .get ('x_0' , 0.0 ) # always in metres in PROJ4 dict
235241 fn = d .get ('y_0' , 0.0 )
236242
237- return lon_0 , n , c , rho0 , k0_param , fe , fn
243+ return lon_0 , n , c , rho0 , k0_param , fe , fn , to_meter
238244
239245
240246@njit (nogil = True , cache = True )
@@ -1228,16 +1234,25 @@ def _tmerc_params(crs):
12281234 return None
12291235 if d .get ('proj' ) != 'tmerc' :
12301236 return None
1231- # Only handle meter-based CRS; non-meter units (us-ft, ft) need
1232- # conversion that we don't implement yet.
1237+
1238+ # Unit conversion: false easting/northing from to_dict() are in
1239+ # the CRS's native units. The Krueger series works in metres,
1240+ # so we convert fe/fn to metres and return to_meter so the caller
1241+ # can scale the final projected coordinates.
12331242 units = d .get ('units' , 'm' )
1234- if units not in ('m' , None ):
1235- return None
1243+ _UNIT_TO_METER = {
1244+ 'm' : 1.0 ,
1245+ 'us-ft' : 0.3048006096012192 , # US survey foot
1246+ 'ft' : 0.3048 , # international foot
1247+ }
1248+ to_meter = _UNIT_TO_METER .get (units )
1249+ if to_meter is None :
1250+ return None # unsupported unit
12361251
12371252 lon_0 = math .radians (d .get ('lon_0' , 0.0 ))
12381253 lat_0 = math .radians (d .get ('lat_0' , 0.0 ))
12391254 k0 = float (d .get ('k_0' , d .get ('k' , 1.0 )))
1240- fe = d .get ('x_0' , 0.0 )
1255+ fe = d .get ('x_0' , 0.0 ) # always in metres in PROJ4 dict
12411256 fn = d .get ('y_0' , 0.0 )
12421257
12431258 # Compute Zb: northing offset for the origin latitude.
@@ -1260,7 +1275,7 @@ def _tmerc_params(crs):
12601275 dCn_val = _clenshaw_complex_py (_ALPHA , sin2Z , cos2Z , 0.0 , 1.0 )
12611276 Zb = - Qn * (Z + dCn_val )
12621277
1263- return lon_0 , k0 , fe , fn , Zb
1278+ return lon_0 , k0 , fe , fn , Zb , to_meter
12641279
12651280
12661281# ---------------------------------------------------------------------------
@@ -1354,21 +1369,31 @@ def try_numba_transform(src_crs, tgt_crs, chunk_bounds, chunk_shape):
13541369 if _is_geographic_wgs84_or_nad83 (src_epsg ):
13551370 tmerc_p = _tmerc_params (tgt_crs )
13561371 if tmerc_p is not None :
1357- lon0 , k0 , fe , fn , Zb = tmerc_p
1372+ lon0 , k0 , fe , fn , Zb , to_m = tmerc_p
13581373 Qn = k0 * _A_RECT
1359- # fn already includes false northing; Zb is the origin offset
1360- tmerc_inverse (out_x_flat , out_y_flat , src_x_flat , src_y_flat ,
1374+ # Input coords are in native CRS units; convert to metres
1375+ if to_m != 1.0 :
1376+ out_x_m = out_x_flat * to_m
1377+ out_y_m = out_y_flat * to_m
1378+ else :
1379+ out_x_m = out_x_flat
1380+ out_y_m = out_y_flat
1381+ tmerc_inverse (out_x_m , out_y_m , src_x_flat , src_y_flat ,
13611382 lon0 , k0 , fe , fn + Zb , Qn , _BETA , _CGB )
13621383 return (src_y_flat .reshape (height , width ),
13631384 src_x_flat .reshape (height , width ))
13641385
13651386 if _is_geographic_wgs84_or_nad83 (tgt_epsg ):
13661387 tmerc_p = _tmerc_params (src_crs )
13671388 if tmerc_p is not None :
1368- lon0 , k0 , fe , fn , Zb = tmerc_p
1389+ lon0 , k0 , fe , fn , Zb , to_m = tmerc_p
13691390 Qn = k0 * _A_RECT
1391+ # tmerc_forward outputs metres; convert back to native units
13701392 tmerc_forward (out_x_flat , out_y_flat , src_x_flat , src_y_flat ,
13711393 lon0 , k0 , fe , fn + Zb , Qn , _ALPHA , _CBG )
1394+ if to_m != 1.0 :
1395+ src_x_flat /= to_m
1396+ src_y_flat /= to_m
13721397 return (src_y_flat .reshape (height , width ),
13731398 src_x_flat .reshape (height , width ))
13741399
@@ -1392,18 +1417,27 @@ def try_numba_transform(src_crs, tgt_crs, chunk_bounds, chunk_shape):
13921417 if _is_geographic_wgs84_or_nad83 (src_epsg ):
13931418 params = _lcc_params (tgt_crs )
13941419 if params is not None :
1395- lon0 , nn , c , rho0 , k0 , fe , fn = params
1396- lcc_inverse (out_x_flat , out_y_flat , src_x_flat , src_y_flat ,
1420+ lon0 , nn , c , rho0 , k0 , fe , fn , to_m = params
1421+ if to_m != 1.0 :
1422+ out_x_m = out_x_flat * to_m
1423+ out_y_m = out_y_flat * to_m
1424+ else :
1425+ out_x_m = out_x_flat
1426+ out_y_m = out_y_flat
1427+ lcc_inverse (out_x_m , out_y_m , src_x_flat , src_y_flat ,
13971428 lon0 , nn , c , rho0 , k0 , fe , fn , _WGS84_E , _WGS84_A )
13981429 return (src_y_flat .reshape (height , width ),
13991430 src_x_flat .reshape (height , width ))
14001431
14011432 if _is_geographic_wgs84_or_nad83 (tgt_epsg ):
14021433 params = _lcc_params (src_crs )
14031434 if params is not None :
1404- lon0 , nn , c , rho0 , k0 , fe , fn = params
1435+ lon0 , nn , c , rho0 , k0 , fe , fn , to_m = params
14051436 lcc_forward (out_x_flat , out_y_flat , src_x_flat , src_y_flat ,
14061437 lon0 , nn , c , rho0 , k0 , fe , fn , _WGS84_E , _WGS84_A )
1438+ if to_m != 1.0 :
1439+ src_x_flat /= to_m
1440+ src_y_flat /= to_m
14071441 return (src_y_flat .reshape (height , width ),
14081442 src_x_flat .reshape (height , width ))
14091443
0 commit comments