@@ -2099,6 +2099,209 @@ def try_numba_transform(src_crs, tgt_crs, chunk_bounds, chunk_shape):
20992099 return None
21002100
21012101
2102+ def transform_points (src_crs , tgt_crs , xs , ys ):
2103+ """Transform scatter points from *src_crs* to *tgt_crs* using Numba kernels.
2104+
2105+ Parameters
2106+ ----------
2107+ src_crs, tgt_crs : CRS-like
2108+ Source and target coordinate reference systems (pyproj CRS or lite CRS).
2109+ xs, ys : array-like
2110+ 1-D arrays of x and y coordinates in *src_crs*.
2111+
2112+ Returns
2113+ -------
2114+ (tx, ty) : tuple of numpy arrays, or None
2115+ Transformed coordinates in *tgt_crs*, or ``None`` if no fast path
2116+ exists for this CRS pair.
2117+
2118+ Notes
2119+ -----
2120+ Intentional omissions (fall back to pyproj for these):
2121+
2122+ * No datum-shift wrapping -- metre-level error is sub-pixel for the
2123+ boundary-estimation use case this function targets.
2124+ * Sinusoidal and Generic Transverse Mercator are not covered here;
2125+ those projections are dispatched via ``to_dict()['proj']`` which
2126+ requires a full pyproj CRS.
2127+ """
2128+ src_epsg = _get_epsg (src_crs )
2129+ tgt_epsg = _get_epsg (tgt_crs )
2130+ if src_epsg is None and tgt_epsg is None :
2131+ return None
2132+
2133+ src_is_geo = _is_supported_geographic (src_epsg )
2134+ tgt_is_geo = _is_supported_geographic (tgt_epsg )
2135+ if not src_is_geo and not tgt_is_geo :
2136+ return None
2137+
2138+ xs = np .asarray (xs , dtype = np .float64 ).ravel ()
2139+ ys = np .asarray (ys , dtype = np .float64 ).ravel ()
2140+ n = xs .shape [0 ]
2141+ tx = np .empty (n , dtype = np .float64 )
2142+ ty = np .empty (n , dtype = np .float64 )
2143+
2144+ # --- Geographic -> Web Mercator (3857) ---
2145+ if src_is_geo and tgt_epsg == 3857 :
2146+ merc_forward (xs , ys , tx , ty )
2147+ return tx , ty
2148+
2149+ if src_epsg == 3857 and tgt_is_geo :
2150+ merc_inverse (xs , ys , tx , ty )
2151+ return tx , ty
2152+
2153+ # --- Geographic -> UTM ---
2154+ if src_is_geo :
2155+ utm = _utm_params (tgt_epsg )
2156+ if utm is not None :
2157+ lon0 , k0 , fe , fn = utm
2158+ Qn = k0 * _A_RECT
2159+ tmerc_forward (xs , ys , tx , ty ,
2160+ lon0 , k0 , fe , fn , Qn , _ALPHA , _CBG )
2161+ return tx , ty
2162+
2163+ # --- UTM -> Geographic ---
2164+ utm_src = _utm_params (src_epsg )
2165+ if utm_src is not None and tgt_is_geo :
2166+ lon0 , k0 , fe , fn = utm_src
2167+ Qn = k0 * _A_RECT
2168+ tmerc_inverse (xs , ys , tx , ty ,
2169+ lon0 , k0 , fe , fn , Qn , _BETA , _CGB )
2170+ return tx , ty
2171+
2172+ # --- Geographic -> Ellipsoidal Mercator (3395) ---
2173+ if src_is_geo and tgt_epsg == 3395 :
2174+ emerc_forward (xs , ys , tx , ty , 1.0 , _WGS84_E )
2175+ return tx , ty
2176+
2177+ if src_epsg == 3395 and tgt_is_geo :
2178+ emerc_inverse (xs , ys , tx , ty , 1.0 , _WGS84_E )
2179+ return tx , ty
2180+
2181+ # --- Geographic -> LCC ---
2182+ if src_is_geo :
2183+ params = _lcc_params (tgt_crs )
2184+ if params is not None :
2185+ lon0 , nn , c , rho0 , k0 , fe , fn , to_m = params
2186+ lcc_forward (xs , ys , tx , ty ,
2187+ lon0 , nn , c , rho0 , k0 , fe , fn , _WGS84_E , _WGS84_A )
2188+ if to_m != 1.0 :
2189+ tx /= to_m
2190+ ty /= to_m
2191+ return tx , ty
2192+
2193+ # --- LCC -> Geographic ---
2194+ if tgt_is_geo :
2195+ params = _lcc_params (src_crs )
2196+ if params is not None :
2197+ lon0 , nn , c , rho0 , k0 , fe , fn , to_m = params
2198+ # lcc_inverse does NOT take a to_m param; pre-multiply if needed
2199+ if to_m != 1.0 :
2200+ xs = xs * to_m
2201+ ys = ys * to_m
2202+ lcc_inverse (xs , ys , tx , ty ,
2203+ lon0 , nn , c , rho0 , k0 , fe , fn , _WGS84_E , _WGS84_A )
2204+ return tx , ty
2205+
2206+ # --- Geographic -> AEA ---
2207+ if src_is_geo :
2208+ params = _aea_params (tgt_crs )
2209+ if params is not None :
2210+ lon0 , nn , C , rho0 , fe , fn = params
2211+ aea_forward (xs , ys , tx , ty ,
2212+ lon0 , nn , C , rho0 , fe , fn ,
2213+ _WGS84_E , _WGS84_A )
2214+ return tx , ty
2215+
2216+ # --- AEA -> Geographic ---
2217+ if tgt_is_geo :
2218+ params = _aea_params (src_crs )
2219+ if params is not None :
2220+ lon0 , nn , C , rho0 , fe , fn = params
2221+ aea_inverse (xs , ys , tx , ty ,
2222+ lon0 , nn , C , rho0 , fe , fn ,
2223+ _WGS84_E , _WGS84_A , _QP , _APA )
2224+ return tx , ty
2225+
2226+ # --- Geographic -> CEA ---
2227+ if src_is_geo :
2228+ params = _cea_params (tgt_crs )
2229+ if params is not None :
2230+ lon0 , k0 , fe , fn = params
2231+ cea_forward (xs , ys , tx , ty ,
2232+ lon0 , k0 , fe , fn ,
2233+ _WGS84_E , _WGS84_A , _QP )
2234+ return tx , ty
2235+
2236+ # --- CEA -> Geographic ---
2237+ if tgt_is_geo :
2238+ params = _cea_params (src_crs )
2239+ if params is not None :
2240+ lon0 , k0 , fe , fn = params
2241+ cea_inverse (xs , ys , tx , ty ,
2242+ lon0 , k0 , fe , fn ,
2243+ _WGS84_E , _WGS84_A , _QP , _APA )
2244+ return tx , ty
2245+
2246+ # --- Geographic -> LAEA ---
2247+ if src_is_geo :
2248+ params = _laea_params (tgt_crs )
2249+ if params is not None :
2250+ lon0 , lat0 , sinb1 , cosb1 , dd , xmf , ymf , rq , qp , fe , fn , mode = params
2251+ laea_forward (xs , ys , tx , ty ,
2252+ lon0 , sinb1 , cosb1 , xmf , ymf , rq , qp ,
2253+ fe , fn , _WGS84_E , _WGS84_A , _WGS84_E2 , mode )
2254+ return tx , ty
2255+
2256+ # --- LAEA -> Geographic ---
2257+ if tgt_is_geo :
2258+ params = _laea_params (src_crs )
2259+ if params is not None :
2260+ lon0 , lat0 , sinb1 , cosb1 , dd , xmf , ymf , rq , qp , fe , fn , mode = params
2261+ laea_inverse (xs , ys , tx , ty ,
2262+ lon0 , sinb1 , cosb1 , xmf , ymf , rq , qp ,
2263+ fe , fn , _WGS84_E , _WGS84_A , _WGS84_E2 , mode , _APA )
2264+ return tx , ty
2265+
2266+ # --- Geographic -> Polar Stereographic ---
2267+ if src_is_geo :
2268+ params = _stere_params (tgt_crs )
2269+ if params is not None :
2270+ lon0 , k0 , akm1 , fe , fn , is_south = params
2271+ stere_forward (xs , ys , tx , ty ,
2272+ lon0 , akm1 , fe , fn , _WGS84_E , is_south )
2273+ return tx , ty
2274+
2275+ # --- Polar Stereographic -> Geographic ---
2276+ if tgt_is_geo :
2277+ params = _stere_params (src_crs )
2278+ if params is not None :
2279+ lon0 , k0 , akm1 , fe , fn , is_south = params
2280+ stere_inverse (xs , ys , tx , ty ,
2281+ lon0 , akm1 , fe , fn , _WGS84_E , is_south )
2282+ return tx , ty
2283+
2284+ # --- Geographic -> Oblique Stereographic ---
2285+ if src_is_geo :
2286+ params = _sterea_params (tgt_crs )
2287+ if params is not None :
2288+ lon0 , sinc0 , cosc0 , R2 , C , K , ratexp , fe , fn , e = params
2289+ sterea_forward (xs , ys , tx , ty ,
2290+ lon0 , sinc0 , cosc0 , R2 , C , K , ratexp , fe , fn , e )
2291+ return tx , ty
2292+
2293+ # --- Oblique Stereographic -> Geographic ---
2294+ if tgt_is_geo :
2295+ params = _sterea_params (src_crs )
2296+ if params is not None :
2297+ lon0 , sinc0 , cosc0 , R2 , C , K , ratexp , fe , fn , e = params
2298+ sterea_inverse (xs , ys , tx , ty ,
2299+ lon0 , sinc0 , cosc0 , R2 , C , K , ratexp , fe , fn , e )
2300+ return tx , ty
2301+
2302+ return None
2303+
2304+
21022305# Wrap try_numba_transform with datum shift support
21032306_try_numba_transform_inner = try_numba_transform
21042307
0 commit comments