s+: complete conversion to local code
i hate GIS - geocoders now have to return a bounding box - new SHAREABLE_TEXT_LOCALITY constant, also exposed - _generate_text now does double duty for locality and sharetext generation
This commit is contained in:
parent
d3ada0b386
commit
a9e26c8916
315
playground.ipynb
315
playground.ipynb
|
@ -42,7 +42,7 @@
|
|||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
|
@ -356,166 +356,289 @@
|
|||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## 2.1.0: the adventure of shortening global/full Plus Codes"
|
||||
"## 2.1.0: adventures in of shortening global/full Plus Codes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### testing rate-limited default geocoding functions"
|
||||
"### testing rate-limited and cached default geocoding functions"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"test_geocoding = SurplusDefaultGeocoding(user_agent=\"surplus/playground\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"1.33318835, 103.77461234638255\n",
|
||||
"{'amenity': 'Ngee Ann Polytechnic', 'house_number': '535', 'road': 'Clementi Road', 'neighbourhood': 'Ewart Park', 'suburb': 'Bukit Timah', 'city': 'Singapore', 'county': 'Northwest', 'ISO3166-2-lvl6': 'SG-03', 'postcode': '599489', 'country': 'Singapore', 'country_code': 'sg', 'raw': {'place_id': 250910125, 'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright', 'osm_type': 'relation', 'osm_id': 2535118, 'lat': '1.33318835', 'lon': '103.77461234638255', 'class': 'amenity', 'type': 'university', 'place_rank': 30, 'importance': 0.34662169301918117, 'addresstype': 'amenity', 'name': 'Ngee Ann Polytechnic', 'display_name': 'Ngee Ann Polytechnic, 535, Clementi Road, Ewart Park, Bukit Timah, Singapore, Northwest, 599489, Singapore', 'address': {'amenity': 'Ngee Ann Polytechnic', 'house_number': '535', 'road': 'Clementi Road', 'neighbourhood': 'Ewart Park', 'suburb': 'Bukit Timah', 'city': 'Singapore', 'county': 'Northwest', 'ISO3166-2-lvl6': 'SG-03', 'postcode': '599489', 'country': 'Singapore', 'country_code': 'sg'}, 'boundingbox': ['1.3289692', '1.3372184', '103.7701481', '103.7783945']}, 'latitude': 1.33318835, 'longitude': 103.77461234638255}\n"
|
||||
"1\n",
|
||||
"2\n",
|
||||
"3\n",
|
||||
"4\n",
|
||||
"5\n",
|
||||
"\n",
|
||||
"1\n",
|
||||
"2\n",
|
||||
"3\n",
|
||||
"4\n",
|
||||
"5\n",
|
||||
"\n",
|
||||
"3.1107698050s\t->\t0.0000886890s\t\t(-3.1106811160002508s)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from surplus import SurplusGeocoderProtocol, SurplusReverserProtocol\n",
|
||||
"from timeit import timeit\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"test_geocoding = SurplusDefaultGeocoding(user_agent=\"surplus/playground\")\n",
|
||||
"test_stmt = \"\"\"\\\n",
|
||||
"print(1)\n",
|
||||
"test_geocoding.geocoder(\"Wisma Atria\") # instant\n",
|
||||
"print(2)\n",
|
||||
"test_geocoding.geocoder(\"Temasek Polytechnic\") # after 1 second\n",
|
||||
"print(3)\n",
|
||||
"location = test_geocoding.geocoder(\"Ngee Ann Polytechnic\") # after 1 second\n",
|
||||
"print(4)\n",
|
||||
"test_geocoding.reverser(f\"{location.latitude}, {location.longitude}\") # instant\n",
|
||||
"print(5)\n",
|
||||
"test_geocoding.reverser(f\"{location.latitude}, {location.longitude}\") # instant (cached)\n",
|
||||
"print()\n",
|
||||
"\"\"\"\n",
|
||||
"\n",
|
||||
"print(location := test_geocoding.geocoder(\"Ngee Ann Polytechnic\"))\n",
|
||||
"time_cold_call = timeit(test_stmt, globals=globals(), number=1) # expecting 3-4 seconds\n",
|
||||
"time_2nd_call = timeit(test_stmt, globals=globals(), number=1) # should be instant\n",
|
||||
"\n",
|
||||
"print(reversed := test_geocoding.reverser(f\"{location.latitude}, {location.longitude}\"))"
|
||||
"print(\n",
|
||||
" f\"{time_cold_call:.10f}s\\t->\\t{time_2nd_call:.10f}s\\t\\t({time_2nd_call - time_cold_call}s)\"\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### loop for less information until a local code is made"
|
||||
"### reversing the query latlong and using the address information to form a locality"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# TODO\n",
|
||||
"\n",
|
||||
"test1 = LocalCodeQuery(\"9R3J+R9\", \"Singapore\")\n",
|
||||
"test2 = LocalCodeQuery(\"G227+XF\", \"St Lucia, Queensland, Australia\")\n",
|
||||
"\n",
|
||||
"level = 13"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'suburb': 'Bishan',\n",
|
||||
" 'city': 'Singapore',\n",
|
||||
" 'county': 'Central',\n",
|
||||
" 'ISO3166-2-lvl6': 'SG-01',\n",
|
||||
" 'country': 'Singapore',\n",
|
||||
" 'country_code': 'sg',\n",
|
||||
" 'raw': {'place_id': 251115282,\n",
|
||||
" 'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright',\n",
|
||||
" 'osm_type': 'way',\n",
|
||||
" 'osm_id': 795946716,\n",
|
||||
" 'lat': '1.3519117',\n",
|
||||
" 'lon': '103.8489708',\n",
|
||||
" 'class': 'place',\n",
|
||||
" 'type': 'suburb',\n",
|
||||
" 'place_rank': 19,\n",
|
||||
" 'importance': 0.39184907371668787,\n",
|
||||
" 'addresstype': 'suburb',\n",
|
||||
" 'name': 'Bishan',\n",
|
||||
" 'display_name': 'Bishan, Singapore, Central, Singapore',\n",
|
||||
" 'address': {'suburb': 'Bishan',\n",
|
||||
" 'city': 'Singapore',\n",
|
||||
" 'county': 'Central',\n",
|
||||
" 'ISO3166-2-lvl6': 'SG-01',\n",
|
||||
" 'country': 'Singapore',\n",
|
||||
" 'country_code': 'sg'},\n",
|
||||
" 'boundingbox': ['1.3416846', '1.3679829', '103.8184512', '103.8604083']},\n",
|
||||
" 'latitude': 1.3519117,\n",
|
||||
" 'longitude': 103.8489708}"
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"St Lucia, St Lucia, Queensland, Australia\n",
|
||||
"Austin, Travis County, Texas, United States\n"
|
||||
]
|
||||
},
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"(\n",
|
||||
" response := geocoding.reverser(\n",
|
||||
" test1.to_lat_long_coord(geocoding.geocoder).get(), level=level\n",
|
||||
" au_response := geocoding.reverser(\n",
|
||||
" (\n",
|
||||
" au_target := (\n",
|
||||
" LocalCodeQuery(\n",
|
||||
" \"G227+XF\", \"St Lucia, Queensland, Australia\"\n",
|
||||
" ).to_lat_long_coord(geocoding.geocoder)\n",
|
||||
" )\n",
|
||||
")"
|
||||
" ).get(),\n",
|
||||
" level=level,\n",
|
||||
" )\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"au_locality = f\"{au_response['suburb']}, {au_response['city_district']}, {au_response['state']}, {au_response['country']}\"\n",
|
||||
"print(au_locality)\n",
|
||||
"\n",
|
||||
"(\n",
|
||||
" us_response := geocoding.reverser(\n",
|
||||
" (\n",
|
||||
" us_target := (\n",
|
||||
" LocalCodeQuery(\"77Q4+7X\", \"Austin, Texas, USA\").to_lat_long_coord(\n",
|
||||
" geocoding.geocoder\n",
|
||||
" )\n",
|
||||
" )\n",
|
||||
" ).get(),\n",
|
||||
" level=level,\n",
|
||||
" )\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"us_locality = f\"{us_response['city']}, {us_response['county']}, {us_response['state']}, {us_response['country']}\"\n",
|
||||
"print(us_locality)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### getting boundary boxes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'suburb': 'St Lucia',\n",
|
||||
" 'city_district': 'St Lucia',\n",
|
||||
" 'city': 'Brisbane City',\n",
|
||||
" 'state': 'Queensland',\n",
|
||||
" 'ISO3166-2-lvl4': 'AU-QLD',\n",
|
||||
" 'postcode': '4072',\n",
|
||||
" 'country': 'Australia',\n",
|
||||
" 'country_code': 'au',\n",
|
||||
" 'raw': {'place_id': 54477898,\n",
|
||||
" 'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. http://osm.org/copyright',\n",
|
||||
" 'osm_type': 'node',\n",
|
||||
" 'osm_id': 88800268,\n",
|
||||
" 'lat': '-27.4987362',\n",
|
||||
" 'lon': '153.0081642',\n",
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'addresstype': 'suburb',\n",
|
||||
" 'boundingbox': ['-27.5187362', '-27.4787362', '152.9881642', '153.0281642'],\n",
|
||||
" 'class': 'place',\n",
|
||||
" 'type': 'suburb',\n",
|
||||
" 'place_rank': 19,\n",
|
||||
" 'importance': 0.27501,\n",
|
||||
" 'addresstype': 'suburb',\n",
|
||||
" 'name': 'St Lucia',\n",
|
||||
" 'display_name': 'St Lucia, Brisbane City, Queensland, 4072, Australia',\n",
|
||||
" 'address': {'suburb': 'St Lucia',\n",
|
||||
" 'city_district': 'St Lucia',\n",
|
||||
" 'city': 'Brisbane City',\n",
|
||||
" 'state': 'Queensland',\n",
|
||||
" 'ISO3166-2-lvl4': 'AU-QLD',\n",
|
||||
" 'postcode': '4072',\n",
|
||||
" 'country': 'Australia',\n",
|
||||
" 'country_code': 'au'},\n",
|
||||
" 'boundingbox': ['-27.5187362', '-27.4787362', '152.9881642', '153.0281642']},\n",
|
||||
" 'latitude': -27.4987362,\n",
|
||||
" 'longitude': 153.0081642}"
|
||||
" 'importance': 0.27501,\n",
|
||||
" 'lat': '-27.4987362',\n",
|
||||
" 'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. '\n",
|
||||
" 'http://osm.org/copyright',\n",
|
||||
" 'lon': '153.0081642',\n",
|
||||
" 'name': 'St Lucia',\n",
|
||||
" 'osm_id': 88800268,\n",
|
||||
" 'osm_type': 'node',\n",
|
||||
" 'place_id': 54477898,\n",
|
||||
" 'place_rank': 19,\n",
|
||||
" 'type': 'suburb'}\n",
|
||||
"\n",
|
||||
"Latlong(latitude=-27.4987362, longitude=153.0081642, bounding_box=[-27.5187362, -27.4787362, 152.9881642, 153.0281642])\n"
|
||||
]
|
||||
},
|
||||
"execution_count": 13,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"(\n",
|
||||
" response := geocoding.reverser(\n",
|
||||
" test2.to_lat_long_coord(geocoding.geocoder).get(), level=level\n",
|
||||
"from geopy.geocoders import Nominatim\n",
|
||||
"from pprint import pprint\n",
|
||||
"\n",
|
||||
"target_query: Result[Latlong] = au_target\n",
|
||||
"target_locality: str = au_locality\n",
|
||||
"\n",
|
||||
"raw_geocoding = Nominatim(user_agent=\"surplus/playground\")\n",
|
||||
"latlong = raw_geocoding.geocode(target_locality)\n",
|
||||
"pprint(latlong.raw)\n",
|
||||
"print()\n",
|
||||
"\n",
|
||||
"# done: now implmented in surplus as surplus.Latlong.bounding_box\n",
|
||||
"locality_latlong = geocoding.geocoder(target_locality)\n",
|
||||
"pprint(locality_latlong)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"[-27.5187362, -27.4787362, 152.9881642, 153.0281642]\n",
|
||||
"(True, True, True, True)\n",
|
||||
"(True, True, True, True)\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# based on <https://github.com/google/open-location-code/wiki/Guidance-for-shortening-codes>\n",
|
||||
"\n",
|
||||
"target_latlong = target_query.get()\n",
|
||||
"if locality_latlong.bounding_box is None:\n",
|
||||
" ... # raise some error\n",
|
||||
"\n",
|
||||
"print(locality_latlong.bounding_box)\n",
|
||||
"check1 = (\n",
|
||||
" # The center point of the feature is within 0.4 degrees latitude and 0.4 degrees longitude\n",
|
||||
" (\n",
|
||||
" (target_latlong.latitude - 0.4)\n",
|
||||
" <= locality_latlong.latitude\n",
|
||||
" <= (target_latlong.latitude + 0.4)\n",
|
||||
" ),\n",
|
||||
" (\n",
|
||||
" (target_latlong.longitude - 0.4)\n",
|
||||
" <= locality_latlong.longitude\n",
|
||||
" <= (target_latlong.longitude + 0.4)\n",
|
||||
" ),\n",
|
||||
" # The bounding box of the feature is less than 0.8 degrees high and wide.\n",
|
||||
" abs(locality_latlong.bounding_box[0] - locality_latlong.bounding_box[1]) < 0.8,\n",
|
||||
" abs(locality_latlong.bounding_box[2] - locality_latlong.bounding_box[3]) < 0.8,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"check2 = (\n",
|
||||
" # The center point of the feature is within 0.4 degrees latitude and 0.4 degrees longitude\n",
|
||||
" (\n",
|
||||
" (target_latlong.latitude - 8)\n",
|
||||
" <= locality_latlong.latitude\n",
|
||||
" <= (target_latlong.latitude + 8)\n",
|
||||
" ),\n",
|
||||
" (\n",
|
||||
" (target_latlong.longitude - 8)\n",
|
||||
" <= locality_latlong.longitude\n",
|
||||
" <= (target_latlong.longitude + 8)\n",
|
||||
" ),\n",
|
||||
" # The bounding box of the feature is less than 0.8 degrees high and wide.\n",
|
||||
" abs(locality_latlong.bounding_box[0] - locality_latlong.bounding_box[1]) < 16,\n",
|
||||
" abs(locality_latlong.bounding_box[2] - locality_latlong.bounding_box[3]) < 16,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"print(check1)\n",
|
||||
"print(check2)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"G227+XF St Lucia, St Lucia, Queensland, Australia\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from pluscodes import encode\n",
|
||||
"\n",
|
||||
"target_plus_code = encode(\n",
|
||||
" lat=target_latlong.latitude, lon=target_latlong.longitude, code_length=10\n",
|
||||
")\n",
|
||||
"portion_plus_code = \"\"\n",
|
||||
"\n",
|
||||
"if check1:\n",
|
||||
" portion_plus_code = target_plus_code[4:]\n",
|
||||
" print(portion_plus_code, target_locality)\n",
|
||||
"\n",
|
||||
"elif check2:\n",
|
||||
" portion_plus_code = target_plus_code[2:]\n",
|
||||
" print(portion_plus_code, target_locality)\n",
|
||||
"\n",
|
||||
"else:\n",
|
||||
" print(\n",
|
||||
" \"info: could not determine a suitable geographical feature to use as locality for shortening.\"\n",
|
||||
" )\n",
|
||||
")"
|
||||
" print(plus_code)"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -48,6 +48,7 @@ from .surplus import (
|
|||
SHAREABLE_TEXT_LINE_4_KEYS,
|
||||
SHAREABLE_TEXT_LINE_5_KEYS,
|
||||
SHAREABLE_TEXT_LINE_6_KEYS,
|
||||
SHAREABLE_TEXT_LOCALITY,
|
||||
SHAREABLE_TEXT_NAMES,
|
||||
VERSION,
|
||||
VERSION_SUFFIX,
|
||||
|
|
|
@ -136,6 +136,13 @@ SHAREABLE_TEXT_NAMES: Final[tuple[str, ...]] = (
|
|||
+ SHAREABLE_TEXT_LINE_2_KEYS
|
||||
+ ("house_name", "road")
|
||||
)
|
||||
SHAREABLE_TEXT_LOCALITY: dict[str, tuple[str, ...]] = {
|
||||
"default": ("city_district", "district", "city", *SHAREABLE_TEXT_LINE_6_KEYS),
|
||||
"SG": ("country",),
|
||||
}
|
||||
|
||||
# adjusts geocoder zoom level when geocoding latlong into an address
|
||||
LOCALITY_GEOCODER_LEVEL: int = 13
|
||||
|
||||
# exceptions
|
||||
|
||||
|
@ -169,6 +176,19 @@ class EmptyQueryError(SurplusException):
|
|||
# data structures
|
||||
|
||||
|
||||
class TextGenerationEnum(Enum):
|
||||
"""
|
||||
(internal use) enum representing what type of text to generate for _generate_text()
|
||||
|
||||
values
|
||||
SHAREABLE_TEXT: str = "sharetext"
|
||||
LOCAL_CODE: str = "localcode"
|
||||
"""
|
||||
|
||||
SHAREABLE_TEXT: str = "sharetext"
|
||||
LOCALITY_TEXT: str = "locality_text"
|
||||
|
||||
|
||||
class ConversionResultTypeEnum(Enum):
|
||||
"""
|
||||
enum representing what the result type of conversion should be
|
||||
|
@ -273,11 +293,16 @@ class Result(NamedTuple, Generic[ResultType]):
|
|||
|
||||
class Latlong(NamedTuple):
|
||||
"""
|
||||
typing.NamedTuple representing a latitude-longitude coordinate pair
|
||||
typing.NamedTuple representing a latitude-longitude coordinate pair and any extra
|
||||
information
|
||||
|
||||
arguments
|
||||
latitude: float
|
||||
longitude: float
|
||||
bounding_box: tuple[float, float, float, float] | None = None
|
||||
a four-tuple representing a bounding box, (lat1, lat2, lon1, lon2) or None
|
||||
the user does not need to enter this. this attribute is only used for
|
||||
shortening plus codes, and will be supplied by the geocoding service.
|
||||
|
||||
methods
|
||||
def __str__(self) -> str: ...
|
||||
|
@ -285,6 +310,7 @@ class Latlong(NamedTuple):
|
|||
|
||||
latitude: float
|
||||
longitude: float
|
||||
bounding_box: tuple[float, float, float, float] | None = None
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""
|
||||
|
@ -306,8 +332,11 @@ class SurplusGeocoderProtocol(Protocol):
|
|||
|
||||
name string to location function. must take in a string and return a Latlong.
|
||||
|
||||
function can be functools.lru_cache()-wrapped if the geocoding service asks for
|
||||
caching
|
||||
**the function returned MUST supply a `bounding_box` attribute to the to-be-returned
|
||||
[Latlong](#class-latlong).** the bounding box is used when surplus shortens Plus Codes.
|
||||
|
||||
function can and should be at minimum functools.lru_cache()-wrapped if the geocoding
|
||||
service asks for caching
|
||||
|
||||
exceptions are handled by the caller
|
||||
"""
|
||||
|
@ -346,8 +375,8 @@ class SurplusReverserProtocol(Protocol):
|
|||
'raw': {...},
|
||||
}
|
||||
|
||||
function can be functools.lru_cache()-wrapped if the geocoding service asks for
|
||||
caching
|
||||
function can and should be at minimum functools.lru_cache()-wrapped if the geocoding
|
||||
service asks for caching
|
||||
|
||||
exceptions are handled by the caller,
|
||||
see the playground notebook in repository root for sample output
|
||||
|
@ -673,9 +702,24 @@ class SurplusDefaultGeocoding:
|
|||
f"No suitable location could be geolocated from '{place}'"
|
||||
)
|
||||
|
||||
bounding_box: tuple[float, float, float, float] | None = location.raw.get(
|
||||
"boundingbox", None
|
||||
)
|
||||
|
||||
if location.raw.get("boundingbox", None) is not None:
|
||||
_bounding_box = [float(c) for c in location.raw.get("boundingbox", [])]
|
||||
if len(_bounding_box) == 4:
|
||||
bounding_box = (
|
||||
_bounding_box[0],
|
||||
_bounding_box[1],
|
||||
_bounding_box[2],
|
||||
_bounding_box[3],
|
||||
)
|
||||
|
||||
return Latlong(
|
||||
latitude=location.latitude,
|
||||
longitude=location.longitude,
|
||||
bounding_box=bounding_box,
|
||||
)
|
||||
|
||||
def reverser(self, latlong: Latlong, level: int = 18) -> dict[str, Any]:
|
||||
|
@ -906,7 +950,12 @@ def parse_query(behaviour: Behaviour) -> Result[Query]:
|
|||
split_query = behaviour.query
|
||||
|
||||
if behaviour.debug:
|
||||
print(f"debug: {split_query=}\ndebug: {original_query=}", file=behaviour.stderr)
|
||||
print(
|
||||
f"debug: parse_query: {split_query=}\n",
|
||||
f"debug: parse_query: {original_query=}",
|
||||
sep="",
|
||||
file=behaviour.stderr,
|
||||
)
|
||||
|
||||
# not a plus/local code, try to match for latlong or string query
|
||||
match split_query:
|
||||
|
@ -921,12 +970,7 @@ def parse_query(behaviour: Behaviour) -> Result[Query]:
|
|||
else: # has comma, possibly a latlong coord
|
||||
comma_split_single: list[str] = single.split(",")
|
||||
|
||||
if len(comma_split_single) > 2:
|
||||
return Result[Query](
|
||||
LatlongQuery(EMPTY_LATLONG),
|
||||
error=LatlongParseError("unable to parse latlong coord"),
|
||||
)
|
||||
|
||||
if len(comma_split_single) == 2:
|
||||
try: # try to type cast query
|
||||
latitude = float(comma_split_single[0].strip(","))
|
||||
longitude = float(comma_split_single[-1].strip(","))
|
||||
|
@ -944,6 +988,9 @@ def parse_query(behaviour: Behaviour) -> Result[Query]:
|
|||
)
|
||||
)
|
||||
|
||||
# not a latlong coord, fallback
|
||||
return Result[Query](StringQuery(original_query))
|
||||
|
||||
case [left_single, right_single]:
|
||||
# possibly a:
|
||||
# space-seperated latlong coord
|
||||
|
@ -1065,9 +1112,27 @@ def _unique(l: Sequence[str]) -> list[str]:
|
|||
|
||||
|
||||
def _generate_text(
|
||||
location: dict[str, Any], behaviour: Behaviour, debug: bool = False
|
||||
location: dict[str, Any],
|
||||
behaviour: Behaviour,
|
||||
mode: TextGenerationEnum = TextGenerationEnum.SHAREABLE_TEXT,
|
||||
debug: bool = False,
|
||||
) -> str:
|
||||
"""(internal function) generate shareable text from location dict"""
|
||||
"""
|
||||
(internal function) generate shareable text from location dict
|
||||
|
||||
arguments
|
||||
location: dict[str, Any]
|
||||
dictionary from geocoding reverser function
|
||||
behaviour: Behaviour
|
||||
surplus behaviour
|
||||
mode: GenerationModeEnum = GenerationModeEnum.SHAREABLE_TEXT
|
||||
generation mode, defaults to shareable text generation
|
||||
debug: bool = False
|
||||
behaviour-seperate debug flag because this function is called twice by
|
||||
surplus in debug mode, one for debug and one for non-debug output
|
||||
|
||||
returns str
|
||||
"""
|
||||
|
||||
def _generate_text_line(
|
||||
line_number: int,
|
||||
|
@ -1083,6 +1148,8 @@ def _generate_text(
|
|||
line number to prefix with
|
||||
line_keys: Sequence[str]
|
||||
list of keys to .get() from location dict
|
||||
seperator: str = ", "
|
||||
seperator to join elements with
|
||||
filter: Callable[[str], list[bool]] = lambda e: True
|
||||
function that takes in a string and returns a list of bools, used to
|
||||
filter elements from line_keys. list will be passed to all(). if all
|
||||
|
@ -1135,6 +1202,14 @@ def _generate_text(
|
|||
if key.lower().startswith("iso3166"):
|
||||
iso3166_2 = location.get(key, "")
|
||||
|
||||
split_iso3166_2 = [part.upper() for part in iso3166_2.split("-")]
|
||||
|
||||
if debug:
|
||||
print(
|
||||
f"debug: _generate_text: {split_iso3166_2=}",
|
||||
file=behaviour.stderr,
|
||||
)
|
||||
|
||||
# skeleton code to allow for changing keys based on iso3166-2 code
|
||||
st_line0_keys = SHAREABLE_TEXT_LINE_0_KEYS
|
||||
st_line1_keys = SHAREABLE_TEXT_LINE_1_KEYS
|
||||
|
@ -1144,12 +1219,32 @@ def _generate_text(
|
|||
st_line5_keys = SHAREABLE_TEXT_LINE_5_KEYS
|
||||
st_line6_keys = SHAREABLE_TEXT_LINE_6_KEYS
|
||||
st_names = SHAREABLE_TEXT_NAMES
|
||||
st_locality: tuple[str, ...] = ()
|
||||
|
||||
match iso3166_2.split("-"):
|
||||
case _:
|
||||
pass
|
||||
match split_iso3166_2:
|
||||
case ["SG", *_]: # Singapore
|
||||
if debug:
|
||||
print(
|
||||
"debug: _generate_text: "
|
||||
f"using special key arrangements for '{iso3166_2}' (Singapore)",
|
||||
file=behaviour.stderr,
|
||||
)
|
||||
|
||||
st_locality = SHAREABLE_TEXT_LOCALITY["SG"]
|
||||
|
||||
case _: # default
|
||||
if debug:
|
||||
print(
|
||||
"debug: _generate_text: "
|
||||
f"using default key arrangements for '{iso3166_2}'",
|
||||
file=behaviour.stderr,
|
||||
)
|
||||
|
||||
st_locality = SHAREABLE_TEXT_LOCALITY["default"]
|
||||
|
||||
# start generating text
|
||||
match mode:
|
||||
case TextGenerationEnum.SHAREABLE_TEXT:
|
||||
text: list[str] = []
|
||||
|
||||
seen_names: list[str] = [
|
||||
|
@ -1167,14 +1262,35 @@ def _generate_text(
|
|||
str(location.get(detail, "")) for detail in st_line6_keys
|
||||
]
|
||||
|
||||
text.append(_generate_text_line(0, st_line0_keys))
|
||||
text.append(_generate_text_line(1, st_line1_keys))
|
||||
text.append(_generate_text_line(2, st_line2_keys))
|
||||
text.append(_generate_text_line(3, st_line3_keys, seperator=" "))
|
||||
text.append(
|
||||
_generate_text_line(
|
||||
4,
|
||||
st_line4_keys,
|
||||
line_number=0,
|
||||
line_keys=st_line0_keys,
|
||||
)
|
||||
)
|
||||
text.append(
|
||||
_generate_text_line(
|
||||
line_number=1,
|
||||
line_keys=st_line1_keys,
|
||||
)
|
||||
)
|
||||
text.append(
|
||||
_generate_text_line(
|
||||
line_number=2,
|
||||
line_keys=st_line2_keys,
|
||||
)
|
||||
)
|
||||
text.append(
|
||||
_generate_text_line(
|
||||
line_number=3,
|
||||
line_keys=st_line3_keys,
|
||||
seperator=" ",
|
||||
)
|
||||
)
|
||||
text.append(
|
||||
_generate_text_line(
|
||||
line_number=4,
|
||||
line_keys=st_line4_keys,
|
||||
filter=lambda ak: [
|
||||
# everything here should be True if the element is to be kept
|
||||
ak not in general_global_info,
|
||||
|
@ -1182,11 +1298,32 @@ def _generate_text(
|
|||
],
|
||||
)
|
||||
)
|
||||
text.append(_generate_text_line(5, st_line5_keys))
|
||||
text.append(_generate_text_line(6, st_line6_keys))
|
||||
text.append(
|
||||
_generate_text_line(
|
||||
line_number=5,
|
||||
line_keys=st_line5_keys,
|
||||
)
|
||||
)
|
||||
text.append(
|
||||
_generate_text_line(
|
||||
line_number=6,
|
||||
line_keys=st_line6_keys,
|
||||
)
|
||||
)
|
||||
|
||||
return "".join(_unique(text)).rstrip()
|
||||
|
||||
case TextGenerationEnum.LOCALITY_TEXT:
|
||||
return _generate_text_line(
|
||||
line_number=0,
|
||||
line_keys=st_locality,
|
||||
)
|
||||
|
||||
case _:
|
||||
raise NotImplementedError(
|
||||
f"unknown mode '{mode}' (expected a TextGenerationEnum)"
|
||||
)
|
||||
|
||||
|
||||
def surplus(query: Query | str, behaviour: Behaviour) -> Result[str]:
|
||||
"""
|
||||
|
@ -1233,17 +1370,17 @@ def surplus(query: Query | str, behaviour: Behaviour) -> Result[str]:
|
|||
return Result[str]("", error=latlong_result.error)
|
||||
|
||||
if behaviour.debug:
|
||||
print(f"debug: cli: {latlong_result.get()=}", file=behaviour.stderr)
|
||||
print(f"debug: {latlong_result.get()=}", file=behaviour.stderr)
|
||||
|
||||
# reverse location and handle result
|
||||
try:
|
||||
location: dict[str, Any] = behaviour.reverser(latlong_result.get())
|
||||
location = behaviour.reverser(latlong_result.get())
|
||||
|
||||
except Exception as exc:
|
||||
return Result[str]("", error=exc)
|
||||
|
||||
if behaviour.debug:
|
||||
print(f"debug: cli: {location=}", file=behaviour.stderr)
|
||||
print(f"debug: {location=}", file=behaviour.stderr)
|
||||
|
||||
# generate text
|
||||
if behaviour.debug:
|
||||
|
@ -1275,7 +1412,7 @@ def surplus(query: Query | str, behaviour: Behaviour) -> Result[str]:
|
|||
return Result[str]("", error=latlong_query.error)
|
||||
|
||||
if behaviour.debug:
|
||||
print(f"debug: cli: {latlong_query.get()=}", file=behaviour.stderr)
|
||||
print(f"debug: {latlong_query.get()=}", file=behaviour.stderr)
|
||||
|
||||
# perform operation
|
||||
try:
|
||||
|
@ -1293,19 +1430,6 @@ def surplus(query: Query | str, behaviour: Behaviour) -> Result[str]:
|
|||
if isinstance(query, LocalCodeQuery):
|
||||
return Result[str](str(query))
|
||||
|
||||
latlong: Latlong = EMPTY_LATLONG
|
||||
|
||||
# if its a plus code, convert to latlong first
|
||||
if isinstance(query, PlusCodeQuery):
|
||||
pluscode_latlong_result = PlusCodeQuery.to_lat_long_coord(
|
||||
query, geocoder=behaviour.geocoder
|
||||
)
|
||||
|
||||
if not pluscode_latlong_result:
|
||||
return Result[str]("", error=pluscode_latlong_result.error)
|
||||
|
||||
latlong = pluscode_latlong_result.get()
|
||||
|
||||
# get latlong and handle result
|
||||
latlong_result = query.to_lat_long_coord(geocoder=behaviour.geocoder)
|
||||
|
||||
|
@ -1313,21 +1437,121 @@ def surplus(query: Query | str, behaviour: Behaviour) -> Result[str]:
|
|||
return Result[str]("", error=latlong_result.error)
|
||||
|
||||
if behaviour.debug:
|
||||
print(f"debug: cli: {latlong_result.get()=}", file=behaviour.stderr)
|
||||
print(f"debug: {latlong_result.get()=}", file=behaviour.stderr)
|
||||
|
||||
latlong = latlong_result.get()
|
||||
query_latlong = latlong_result.get()
|
||||
|
||||
# perform operation
|
||||
# TODO: https://github.com/markjoshwel/surplus/issues/18
|
||||
# https://github.com/google/open-location-code/wiki/Guidance-for-shortening-codes
|
||||
|
||||
return Result[str](
|
||||
text,
|
||||
error=NotImplementedError(
|
||||
"converting to Plus Code is not implemented yet"
|
||||
),
|
||||
# reverse location and handle result
|
||||
try:
|
||||
location = behaviour.reverser(
|
||||
query_latlong, level=LOCALITY_GEOCODER_LEVEL
|
||||
)
|
||||
|
||||
except Exception as exc:
|
||||
return Result[str]("", error=exc)
|
||||
|
||||
if behaviour.debug:
|
||||
print(f"debug: {location=}", file=behaviour.stderr)
|
||||
|
||||
# generate locality portion of local code
|
||||
if behaviour.debug:
|
||||
print(
|
||||
_generate_text(
|
||||
location=location,
|
||||
behaviour=behaviour,
|
||||
mode=TextGenerationEnum.LOCALITY_TEXT,
|
||||
debug=behaviour.debug,
|
||||
).strip()
|
||||
)
|
||||
|
||||
portion_locality: str = _generate_text(
|
||||
location=location,
|
||||
behaviour=behaviour,
|
||||
mode=TextGenerationEnum.LOCALITY_TEXT,
|
||||
).strip()
|
||||
|
||||
# reverse locality portion
|
||||
try:
|
||||
locality_latlong: Latlong = behaviour.geocoder(portion_locality)
|
||||
|
||||
# check now if bounding_box is set and valid
|
||||
assert locality_latlong.bounding_box is not None, (
|
||||
"(shortening) geocoder-returned latlong has .bounding_box=None"
|
||||
f" - {locality_latlong.bounding_box}"
|
||||
)
|
||||
|
||||
assert len(locality_latlong.bounding_box) == 4, (
|
||||
"(shortening) geocoder-returned latlong has len(.bounding_box) < 4"
|
||||
f" - {locality_latlong.bounding_box}"
|
||||
)
|
||||
|
||||
assert all([type(c) == float for c in locality_latlong.bounding_box]), (
|
||||
"(shortening) geocoder-returned latlong has non-float in .bounding_box"
|
||||
f" - {locality_latlong.bounding_box}"
|
||||
)
|
||||
|
||||
except Exception as exc:
|
||||
return Result[str]("", error=exc)
|
||||
|
||||
plus_code = _PlusCode_encode(
|
||||
lat=query_latlong.latitude,
|
||||
lon=query_latlong.longitude,
|
||||
)
|
||||
|
||||
# https://github.com/google/open-location-code/wiki/Guidance-for-shortening-codes
|
||||
check1 = (
|
||||
# The center point of the feature is within 0.4 degrees latitude and 0.4
|
||||
# degrees longitude
|
||||
(
|
||||
(query_latlong.latitude - 0.4)
|
||||
<= locality_latlong.latitude
|
||||
<= (query_latlong.latitude + 0.4)
|
||||
),
|
||||
(
|
||||
(query_latlong.longitude - 0.4)
|
||||
<= locality_latlong.longitude
|
||||
<= (query_latlong.longitude + 0.4)
|
||||
),
|
||||
# The bounding box of the feature is less than 0.8 degrees high and wide.
|
||||
abs(locality_latlong.bounding_box[0] - locality_latlong.bounding_box[1])
|
||||
< 0.8,
|
||||
abs(locality_latlong.bounding_box[2] - locality_latlong.bounding_box[3])
|
||||
< 0.8,
|
||||
)
|
||||
|
||||
check2 = (
|
||||
# The center point of the feature is within 0.4 degrees latitude and 0.4
|
||||
# degrees longitude"
|
||||
(
|
||||
(query_latlong.latitude - 8)
|
||||
<= locality_latlong.latitude
|
||||
<= (query_latlong.latitude + 8)
|
||||
),
|
||||
(
|
||||
(query_latlong.longitude - 8)
|
||||
<= locality_latlong.longitude
|
||||
<= (query_latlong.longitude + 8)
|
||||
),
|
||||
# The bounding box of the feature is less than 0.8 degrees high and wide.
|
||||
abs(locality_latlong.bounding_box[0] - locality_latlong.bounding_box[1])
|
||||
< 16,
|
||||
abs(locality_latlong.bounding_box[2] - locality_latlong.bounding_box[3])
|
||||
< 16,
|
||||
)
|
||||
|
||||
if check1:
|
||||
return Result[str](f"{plus_code[4:]} {portion_locality}")
|
||||
|
||||
elif check2:
|
||||
return Result[str](f"{plus_code[2:]} {portion_locality}")
|
||||
|
||||
print(
|
||||
"info: could not determine a suitable geographical feature to use as "
|
||||
"locality for shortening. full plus code is returned.",
|
||||
file=behaviour.stderr,
|
||||
)
|
||||
return Result[str](plus_code)
|
||||
|
||||
case ConversionResultTypeEnum.LATLONG:
|
||||
# return the latlong if already given a latlong
|
||||
if isinstance(query, LatlongQuery):
|
||||
|
@ -1340,7 +1564,7 @@ def surplus(query: Query | str, behaviour: Behaviour) -> Result[str]:
|
|||
return Result[str]("", error=latlong_result.error)
|
||||
|
||||
if behaviour.debug:
|
||||
print(f"debug: cli: {latlong_result.get()=}", file=behaviour.stderr)
|
||||
print(f"debug: {latlong_result.get()=}", file=behaviour.stderr)
|
||||
|
||||
# perform operation
|
||||
return Result[str](str(latlong_result.get()))
|
||||
|
|
Loading…
Reference in a new issue