diff --git a/core/proj/reproj.py b/core/proj/reproj.py index f6a7e7a8..c57f5396 100755 --- a/core/proj/reproj.py +++ b/core/proj/reproj.py @@ -23,7 +23,7 @@ from .srs import SRS from .utm import UTM, UTM_EPSG_CODES from .ellps import GRS80 -from .srv import EPSGIO +from .srv import MapTilerCoordinates from ..errors import ReprojError from ..utils import BBOX diff --git a/core/proj/srv.py b/core/proj/srv.py index 11263c05..1fc6ad39 100755 --- a/core/proj/srv.py +++ b/core/proj/srv.py @@ -32,15 +32,18 @@ REPROJ_TIMEOUT = 60 ###################################### -# EPSG.io -# https://github.com/klokantech/epsg.io +# MapTiler Coordinates API (formerly EPSG.io) +# Migration guide: https://docs.maptiler.com/cloud/api/coordinates/ - -class EPSGIO(): +class MapTilerCoordinates(): @staticmethod - def ping(): - url = "http://epsg.io" + def ping(api_key=None): + """Test connection to MapTiler API server""" + if api_key is None: + api_key = settings.maptiler_api_key + + url = "https://api.maptiler.com/coordinates/search/epsg.json?key=" + api_key try: rq = Request(url, headers={'User-Agent': USER_AGENT}) urlopen(rq, timeout=DEFAULT_TIMEOUT) @@ -54,17 +57,20 @@ def ping(): except: raise - @staticmethod - def reprojPt(epsg1, epsg2, x1, y1): - - url = "http://epsg.io/trans?x={X}&y={Y}&z={Z}&s_srs={CRS1}&t_srs={CRS2}" + def reprojPt(epsg1, epsg2, x1, y1, api_key=None): + """Reproject a single point using MapTiler Coordinates API""" + if api_key is None: + api_key = settings.maptiler_api_key + + # New endpoint format with API key + url = "https://api.maptiler.com/coordinates/transform/{X},{Y}.json?s_srs={CRS1}&t_srs={CRS2}&key={KEY}" url = url.replace("{X}", str(x1)) url = url.replace("{Y}", str(y1)) - url = url.replace("{Z}", '0') url = url.replace("{CRS1}", str(epsg1)) url = url.replace("{CRS2}", str(epsg2)) + url = url.replace("{KEY}", api_key) log.debug(url) @@ -77,38 +83,37 @@ def reprojPt(epsg1, epsg2, x1, y1): obj = json.loads(response) - return (float(obj['x']), float(obj['y'])) + # The MapTiler response format is different from the old EPSG.io format + # MapTiler returns coordinates as an array in the response body + return (float(obj[0]), float(obj[1])) @staticmethod - def reprojPts(epsg1, epsg2, points): - + def reprojPts(epsg1, epsg2, points, api_key=None): + """Reproject multiple points using MapTiler Coordinates API""" + if api_key is None: + api_key = settings.maptiler_api_key + if len(points) == 1: x, y = points[0] - return [EPSGIO.reprojPt(epsg1, epsg2, x, y)] + return [MapTilerCoordinates.reprojPt(epsg1, epsg2, x, y, api_key=api_key)] - urlTemplate = "http://epsg.io/trans?data={POINTS}&s_srs={CRS1}&t_srs={CRS2}" + # New endpoint with batch transformation (up to 50 points) + urlTemplate = "https://api.maptiler.com/coordinates/transform/{POINTS}.json?s_srs={CRS1}&t_srs={CRS2}&key={KEY}" urlTemplate = urlTemplate.replace("{CRS1}", str(epsg1)) urlTemplate = urlTemplate.replace("{CRS2}", str(epsg2)) - - #data = ';'.join([','.join(map(str, p)) for p in points]) + urlTemplate = urlTemplate.replace("{KEY}", api_key) precision = 4 - data = [','.join( [str(round(v, precision)) for v in p] ) for p in points ] - part, parts = [], [] - for i,p in enumerate(data): - l = sum([len(p) for p in part]) + len(';'*len(part)) - if l + len(p) < 4000: #limit is 4094 - part.append(p) - else: - parts.append(part) - part = [p] - if i == len(data)-1: - parts.append(part) - parts = [';'.join(part) for part in parts] - + data = [','.join([str(round(v, precision)) for v in p]) for p in points] + + # MapTiler API supports up to 50 points per request in batch mode + batch_size = 50 + batches = [data[i:i + batch_size] for i in range(0, len(data), batch_size)] + result = [] - for part in parts: + for batch in batches: + part = ';'.join(batch) url = urlTemplate.replace("{POINTS}", part) log.debug(url) @@ -120,32 +125,58 @@ def reprojPts(epsg1, epsg2, points): raise obj = json.loads(response) - result.extend( [(float(p['x']), float(p['y'])) for p in obj] ) + + # MapTiler API returns an array of coordinate pairs + result.extend([(float(p[0]), float(p[1])) for p in obj]) return result @staticmethod - def search(query): + def search(query, api_key=None): + """Search coordinate systems using MapTiler Coordinates API""" + if api_key is None: + api_key = settings.maptiler_api_key + query = str(query).replace(' ', '+') - url = "http://epsg.io/?q={QUERY}&format=json" + # New endpoint with API key + url = "https://api.maptiler.com/coordinates/search/{QUERY}.json?key={KEY}" url = url.replace("{QUERY}", query) + url = url.replace("{KEY}", api_key) + log.debug('Search crs : {}'.format(url)) rq = Request(url, headers={'User-Agent': USER_AGENT}) response = urlopen(rq, timeout=DEFAULT_TIMEOUT).read().decode('utf8') obj = json.loads(response) - log.debug('Search results : {}'.format([ (r['code'], r['name']) for r in obj['results'] ])) + + log.debug('Search results : {}'.format([(r['id']['code'], r['name']) for r in obj['results']])) return obj['results'] @staticmethod - def getEsriWkt(epsg): - url = "http://epsg.io/{CODE}.esriwkt" + def getEsriWkt(epsg, api_key=None): + """Get ESRI WKT for a specific EPSG code using MapTiler Coordinates API""" + if api_key is None: + api_key = settings.maptiler_api_key + + # New endpoint with API key + url = "https://api.maptiler.com/coordinates/search/{CODE}.json?exports=true&key={KEY}" url = url.replace("{CODE}", str(epsg)) + url = url.replace("{KEY}", api_key) + log.debug(url) rq = Request(url, headers={'User-Agent': USER_AGENT}) - wkt = urlopen(rq, timeout=DEFAULT_TIMEOUT).read().decode('utf8') - return wkt + response = urlopen(rq, timeout=DEFAULT_TIMEOUT).read().decode('utf8') + obj = json.loads(response) + + if obj['results'] and len(obj['results']) > 0 and 'exports' in obj['results'][0]: + return obj['results'][0]['exports']['esriwkt'] + else: + log.error('Could not find ESRI WKT for EPSG:{}'.format(epsg)) + return None +# For backward compatibility, you can keep the EPSGIO class as an alias to MapTilerCoordinates +class EPSGIO(MapTilerCoordinates): + pass ###################################### @@ -173,11 +204,10 @@ def reprojPt(epsg1, epsg2, x1, y1): ###################################### -#http://spatialreference.org/ref/epsg/2154/esriwkt/ - -#class SpatialRefOrg(): +# http://spatialreference.org/ref/epsg/2154/esriwkt/ +# class SpatialRefOrg(): ###################################### -#http://prj2epsg.org/search +# http://prj2epsg.org/search \ No newline at end of file diff --git a/prefs.py b/prefs.py index 4d4eec3a..85155b80 100755 --- a/prefs.py +++ b/prefs.py @@ -9,7 +9,7 @@ import addon_utils from . import bl_info -from .core.proj.reproj import EPSGIO +from .core.proj.reproj import MapTilerCoordinates from .core.proj.srs import SRS from .core.checkdeps import HAS_GDAL, HAS_PYPROJ, HAS_PIL, HAS_IMGIO from .core import settings @@ -241,7 +241,10 @@ def listDemServer(self, context): name = "", description="you need to register and request a key from opentopography website" ) - + maptiler_api_key: StringProperty( + name = "", + description = "API key for MapTiler Coordinates API (required for EPSG.io migration)" + ) ################ #IO options @@ -333,6 +336,11 @@ def draw(self, context): row = box.row() row.label(text="Opentopography Api Key") box.row().prop(self, "opentopography_api_key") + + row = box.row() + row.label(text="MapTiler API Key") + box.row().prop(self, "maptiler_api_key") + #System box = layout.box() box.label(text='System') @@ -387,21 +395,28 @@ def check(self, context): return True def search(self, context): - if not EPSGIO.ping(): - self.report({'ERROR'}, "Cannot request epsg.io website") + prefs = context.preferences.addons[PKG].preferences + api_key = prefs.maptiler_api_key + + if not api_key: + self.report({'ERROR'}, "MapTiler API key is required. Please set it in the preferences.") + return + + if not MapTilerCoordinates.ping(api_key=api_key): + self.report({'ERROR'}, "Cannot connect to MapTiler API") else: - results = EPSGIO.search(self.query) + results = MapTilerCoordinates.search(self.query, api_key=api_key) self.results = json.dumps(results) if results: - self.crs = 'EPSG:' + results[0]['code'] + self.crs = 'EPSG:' + str(results[0]['id']['code']) self.name = results[0]['name'] def updEnum(self, context): crsItems = [] if self.results != '': for result in json.loads(self.results): - srid = 'EPSG:' + result['code'] - crsItems.append( (result['code'], result['name'], srid) ) + srid = 'EPSG:' + str(result['id']['code']) + crsItems.append( (str(result['id']['code']), result['name'], srid) ) return crsItems def fill(self, context):