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:
Mark Joshwel 2023-09-06 17:39:53 +00:00
parent d3ada0b386
commit a9e26c8916
3 changed files with 536 additions and 188 deletions

View file

@ -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}"
]
},
"execution_count": 12,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"St Lucia, St Lucia, Queensland, Australia\n",
"Austin, Travis County, Texas, United States\n"
]
}
],
"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",
" '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}"
]
},
"execution_count": 13,
"metadata": {},
"output_type": "execute_result"
"name": "stdout",
"output_type": "stream",
"text": [
"{'addresstype': 'suburb',\n",
" 'boundingbox': ['-27.5187362', '-27.4787362', '152.9881642', '153.0281642'],\n",
" 'class': 'place',\n",
" 'display_name': 'St Lucia, Brisbane City, Queensland, 4072, Australia',\n",
" '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"
]
}
],
"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)"
]
},
{

View file

@ -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,

View file

@ -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,28 +970,26 @@ 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(","))
try: # try to type cast query
latitude = float(comma_split_single[0].strip(","))
longitude = float(comma_split_single[-1].strip(","))
except ValueError: # not a latlong coord, fallback
return Result[Query](StringQuery(single))
except ValueError: # not a latlong coord, fallback
return Result[Query](StringQuery(single))
else: # are floats, so is a latlong coord
return Result[Query](
LatlongQuery(
Latlong(
latitude=latitude,
longitude=longitude,
else: # are floats, so is a latlong coord
return Result[Query](
LatlongQuery(
Latlong(
latitude=latitude,
longitude=longitude,
)
)
)
)
# not a latlong coord, fallback
return Result[Query](StringQuery(original_query))
case [left_single, right_single]:
# possibly a:
@ -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,48 +1219,110 @@ 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
text: list[str] = []
match mode:
case TextGenerationEnum.SHAREABLE_TEXT:
text: list[str] = []
seen_names: list[str] = [
detail
for detail in _unique(
[str(location.get(location_key, "")) for location_key in st_names]
)
if detail != ""
]
seen_names: list[str] = [
detail
for detail in _unique(
[str(location.get(location_key, "")) for location_key in st_names]
)
if detail != ""
]
if debug:
print(f"debug: _generate_text: {seen_names=}", file=behaviour.stderr)
if debug:
print(f"debug: _generate_text: {seen_names=}", file=behaviour.stderr)
general_global_info: list[str] = [
str(location.get(detail, "")) for detail in st_line6_keys
]
general_global_info: list[str] = [
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,
filter=lambda ak: [
# everything here should be True if the element is to be kept
ak not in general_global_info,
not any(True if (ak in sn) else False for sn in seen_names),
],
)
)
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=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,
not any(True if (ak in sn) else False for sn in seen_names),
],
)
)
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()
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
# reverse location and handle result
try:
location = behaviour.reverser(
query_latlong, level=LOCALITY_GEOCODER_LEVEL
)
return Result[str](
text,
error=NotImplementedError(
"converting to Plus Code is not implemented yet"
),
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()))