jageocoder 2.1.7.dev3__tar.gz → 2.1.7.post1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/PKG-INFO +1 -1
- {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/__init__.py +1 -1
- {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/module.py +15 -6
- {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/node.py +11 -0
- {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/remote.py +65 -66
- {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/rtree.py +95 -154
- {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/tree.py +16 -2
- {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/pyproject.toml +1 -1
- {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/LICENSE +0 -0
- {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/README.md +0 -0
- {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/__main__.py +0 -0
- {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/address.py +0 -0
- {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/aza_master.py +0 -0
- {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/dataset.py +0 -0
- {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/exceptions.py +0 -0
- {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/itaiji.py +0 -0
- {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/itaiji_dic.json +0 -0
- {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/result.py +0 -0
- {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/strlib.py +0 -0
- {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/trie.py +0 -0
|
@@ -19,7 +19,7 @@ running the following steps.
|
|
|
19
19
|
>>> jageocoder.searchNode('<Japanese-address>')
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
__version__ = '2.1.7.
|
|
22
|
+
__version__ = '2.1.7.post1' # The package version
|
|
23
23
|
__dictionary_version__ = '20230927' # Compatible dictionary version
|
|
24
24
|
__author__ = 'Takeshi Sagara <sagara@info-proto.com>'
|
|
25
25
|
|
|
@@ -63,16 +63,25 @@ def init(db_dir: Optional[os.PathLike] = None,
|
|
|
63
63
|
|
|
64
64
|
_url = None
|
|
65
65
|
_db_dir = None
|
|
66
|
+
|
|
67
|
+
# Check parameters
|
|
66
68
|
if db_dir is not None:
|
|
67
69
|
_db_dir = db_dir
|
|
68
70
|
elif url is not None and mode == 'r':
|
|
69
71
|
_url = url
|
|
70
|
-
elif os.environ.get('JAGEOCODER_DB2_DIR'):
|
|
71
|
-
_db_dir = get_db_dir(mode=mode)
|
|
72
72
|
|
|
73
|
+
# Check environmental variables
|
|
73
74
|
if _db_dir is None and _url is None:
|
|
74
|
-
|
|
75
|
+
if os.environ.get('JAGEOCODER_DB2_DIR'):
|
|
76
|
+
_db_dir = get_db_dir(mode=mode)
|
|
77
|
+
elif os.environ.get('JAGEOCODER_SERVER_URL') and mode == 'r':
|
|
78
|
+
_url = os.environ.get('JAGEOCODER_SERVER_URL')
|
|
75
79
|
|
|
80
|
+
# Search local database
|
|
81
|
+
if _db_dir is None and _url is None:
|
|
82
|
+
_db_dir = get_db_dir(mode=mode)
|
|
83
|
+
|
|
84
|
+
# Initialize tree object
|
|
76
85
|
if _db_dir:
|
|
77
86
|
_tree = AddressTree(db_dir=_db_dir, mode=mode, debug=debug)
|
|
78
87
|
elif _url:
|
|
@@ -293,7 +302,7 @@ def installed_dictionary_version(
|
|
|
293
302
|
If omitted, it will be determined by `get_db_dir()`.
|
|
294
303
|
|
|
295
304
|
url: str, optional
|
|
296
|
-
URL of the Jageocoder server endpoint.
|
|
305
|
+
URL of the Jageocoder server endpoint.
|
|
297
306
|
|
|
298
307
|
Returns
|
|
299
308
|
-------
|
|
@@ -337,7 +346,7 @@ def installed_dictionary_readme(
|
|
|
337
346
|
If omitted, it will be determined by `get_db_dir()`.
|
|
338
347
|
|
|
339
348
|
url: str, optional
|
|
340
|
-
URL of the Jageocoder server endpoint.
|
|
349
|
+
URL of the Jageocoder server endpoint.
|
|
341
350
|
|
|
342
351
|
Returns
|
|
343
352
|
-------
|
|
@@ -473,7 +482,7 @@ def reverse(
|
|
|
473
482
|
-----
|
|
474
483
|
- The result list contains up to 3 nodes.
|
|
475
484
|
- Each element is a dict type with the following structure:
|
|
476
|
-
{"candidate":AddressNode, "dist":float}
|
|
485
|
+
{"candidate":AddressNode, "dist":float}
|
|
477
486
|
"""
|
|
478
487
|
if not is_initialized():
|
|
479
488
|
raise JageocoderError("Not initialized. Call 'init()' first.")
|
|
@@ -1512,6 +1512,17 @@ class AddressNode(object):
|
|
|
1512
1512
|
|
|
1513
1513
|
return ''
|
|
1514
1514
|
|
|
1515
|
+
def get_machiaza_id(self) -> str:
|
|
1516
|
+
"""
|
|
1517
|
+
Returns the MachiAza ID defined by JDA address-base-registry
|
|
1518
|
+
containing this node.
|
|
1519
|
+
|
|
1520
|
+
Note
|
|
1521
|
+
----
|
|
1522
|
+
- This method is an alias for 'get_aza_id'.
|
|
1523
|
+
"""
|
|
1524
|
+
return self.get_aza_id()
|
|
1525
|
+
|
|
1515
1526
|
def get_aza_code(self) -> str:
|
|
1516
1527
|
"""
|
|
1517
1528
|
Returns the 'AZA-code' concatinated with the city-code
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
2
|
import json
|
|
3
3
|
from logging import getLogger
|
|
4
4
|
import os
|
|
5
5
|
import requests
|
|
6
|
-
from typing import Any, List, NoReturn, Optional
|
|
6
|
+
from typing import Any, List, NoReturn, Optional
|
|
7
7
|
import uuid
|
|
8
8
|
|
|
9
9
|
from jageocoder.address import AddressLevel
|
|
@@ -14,56 +14,17 @@ from jageocoder.tree import AddressTree, LRU
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
logger = getLogger(__name__)
|
|
17
|
-
_session = None
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def _json_request(
|
|
21
|
-
url: str,
|
|
22
|
-
method: str,
|
|
23
|
-
params: object,
|
|
24
|
-
) -> Any:
|
|
25
|
-
global _session
|
|
26
|
-
payload = {
|
|
27
|
-
"jsonrpc": "2.0",
|
|
28
|
-
"method": method,
|
|
29
|
-
"params": params,
|
|
30
|
-
"id": str(uuid.uuid4()),
|
|
31
|
-
}
|
|
32
|
-
if _session is None:
|
|
33
|
-
logger.debug("Start a new HTTP session with the remote tree.")
|
|
34
|
-
_session = requests.Session()
|
|
35
|
-
|
|
36
|
-
logger.debug(
|
|
37
|
-
"Send JSON-RPC request ---\n" +
|
|
38
|
-
json.dumps(payload, indent=2, ensure_ascii=False)
|
|
39
|
-
)
|
|
40
|
-
response = _session.post(
|
|
41
|
-
url=url,
|
|
42
|
-
data=json.dumps(payload),
|
|
43
|
-
headers={"Content-Type": "application/json"}
|
|
44
|
-
).json()
|
|
45
|
-
|
|
46
|
-
if "error" in response:
|
|
47
|
-
raise RemoteTreeException(response["error"])
|
|
48
|
-
|
|
49
|
-
logger.debug(
|
|
50
|
-
"Receive JSON-RPC response ---\n" +
|
|
51
|
-
json.dumps(response["result"], indent=2, ensure_ascii=False)
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
return response["result"]
|
|
55
17
|
|
|
56
18
|
|
|
57
19
|
class RemoteDataset(object):
|
|
58
20
|
|
|
59
|
-
def __init__(self,
|
|
60
|
-
self.
|
|
21
|
+
def __init__(self, tree: RemoteTree) -> None:
|
|
22
|
+
self.tree = tree
|
|
61
23
|
self.records = {}
|
|
62
24
|
self._map = {}
|
|
63
25
|
|
|
64
26
|
def load_record(self, id: int) -> None:
|
|
65
|
-
rpc_result =
|
|
66
|
-
url=self.url,
|
|
27
|
+
rpc_result = self.tree.json_request(
|
|
67
28
|
method="dataset.get",
|
|
68
29
|
params={"id": id},
|
|
69
30
|
)
|
|
@@ -79,9 +40,9 @@ class RemoteDataset(object):
|
|
|
79
40
|
|
|
80
41
|
class RemoteNodeTable(object):
|
|
81
42
|
|
|
82
|
-
def __init__(self,
|
|
83
|
-
self.
|
|
84
|
-
self.datasets = RemoteDataset(
|
|
43
|
+
def __init__(self, tree: RemoteTree) -> None:
|
|
44
|
+
self.tree = tree
|
|
45
|
+
self.datasets = RemoteDataset(tree=tree)
|
|
85
46
|
self.cache = LRU()
|
|
86
47
|
self.server_signature = None
|
|
87
48
|
self.mode = 'r' # Always read only
|
|
@@ -96,8 +57,7 @@ class RemoteNodeTable(object):
|
|
|
96
57
|
that requires dictionary consistency to ensure
|
|
97
58
|
the server has not been restarted during processing.
|
|
98
59
|
"""
|
|
99
|
-
server_signature =
|
|
100
|
-
url=self.url,
|
|
60
|
+
server_signature = self.tree.json_request(
|
|
101
61
|
method="jageocoder.server_signature",
|
|
102
62
|
params=[],
|
|
103
63
|
)
|
|
@@ -126,8 +86,7 @@ class RemoteNodeTable(object):
|
|
|
126
86
|
if pos in self.cache:
|
|
127
87
|
return self.cache[pos]
|
|
128
88
|
|
|
129
|
-
rpc_result =
|
|
130
|
-
url=self.url,
|
|
89
|
+
rpc_result = self.tree.json_request(
|
|
131
90
|
method="node.get_record",
|
|
132
91
|
params={"pos": pos, "server": self.server_signature},
|
|
133
92
|
)
|
|
@@ -166,8 +125,7 @@ class RemoteNodeTable(object):
|
|
|
166
125
|
- TRIE index must be created on the column before searching.
|
|
167
126
|
- The TRIE index file will be automatically opened if it exists.
|
|
168
127
|
"""
|
|
169
|
-
rpc_result =
|
|
170
|
-
url=self.url,
|
|
128
|
+
rpc_result = self.tree.json_request(
|
|
171
129
|
method="node.search_records_on",
|
|
172
130
|
params={
|
|
173
131
|
"attr": attr,
|
|
@@ -204,8 +162,16 @@ class RemoteNodeTable(object):
|
|
|
204
162
|
List[Record]
|
|
205
163
|
List of records.
|
|
206
164
|
"""
|
|
207
|
-
|
|
208
|
-
|
|
165
|
+
rpc_result = self.tree.json_request(
|
|
166
|
+
method="node.search_records_on",
|
|
167
|
+
params={
|
|
168
|
+
"attr": attr,
|
|
169
|
+
"value": value,
|
|
170
|
+
"funcname": "get",
|
|
171
|
+
"server": self.server_signature
|
|
172
|
+
},
|
|
173
|
+
)
|
|
174
|
+
ids = [record["id"] for record in rpc_result]
|
|
209
175
|
return ids
|
|
210
176
|
|
|
211
177
|
|
|
@@ -241,9 +207,10 @@ class RemoteTree(AddressTree):
|
|
|
241
207
|
debug: bool, optional (default=False)
|
|
242
208
|
Debugging flag. If set to True, write debugging messages.
|
|
243
209
|
"""
|
|
210
|
+
self._session = None
|
|
244
211
|
self.url = url
|
|
245
212
|
self.debug = debug
|
|
246
|
-
self.address_nodes = RemoteNodeTable(
|
|
213
|
+
self.address_nodes = RemoteNodeTable(tree=self)
|
|
247
214
|
self.config = {
|
|
248
215
|
'debug': self.debug,
|
|
249
216
|
'aza_skip': os.environ.get('JAGEOCODER_OPT_AZA_SKIP'),
|
|
@@ -294,10 +261,45 @@ class RemoteTree(AddressTree):
|
|
|
294
261
|
self.validate_config(key=k, value=v)
|
|
295
262
|
self.config[k] = v
|
|
296
263
|
|
|
264
|
+
def json_request(
|
|
265
|
+
self,
|
|
266
|
+
method: str,
|
|
267
|
+
params: object,
|
|
268
|
+
) -> Any:
|
|
269
|
+
payload = {
|
|
270
|
+
"jsonrpc": "2.0",
|
|
271
|
+
"method": method,
|
|
272
|
+
"params": params,
|
|
273
|
+
"id": str(uuid.uuid4()),
|
|
274
|
+
}
|
|
275
|
+
if self._session is None:
|
|
276
|
+
logger.debug("Start a new HTTP session with the remote tree.")
|
|
277
|
+
self._session = requests.Session()
|
|
278
|
+
|
|
279
|
+
logger.debug(
|
|
280
|
+
"Send JSON-RPC request ---\n" +
|
|
281
|
+
json.dumps(payload, indent=2, ensure_ascii=False)
|
|
282
|
+
)
|
|
283
|
+
response = self._session.post(
|
|
284
|
+
url=self.url,
|
|
285
|
+
data=json.dumps(payload),
|
|
286
|
+
headers={"Content-Type": "application/json"}
|
|
287
|
+
).json()
|
|
288
|
+
|
|
289
|
+
if "error" in response:
|
|
290
|
+
raise RemoteTreeException(response["error"])
|
|
291
|
+
|
|
292
|
+
logger.debug(
|
|
293
|
+
"Receive JSON-RPC response ---\n" +
|
|
294
|
+
json.dumps(response["result"], indent=2, ensure_ascii=False)
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
return response["result"]
|
|
298
|
+
|
|
297
299
|
def _close(self) -> None:
|
|
298
|
-
if _session:
|
|
299
|
-
del _session
|
|
300
|
-
_session = None
|
|
300
|
+
if self._session:
|
|
301
|
+
del self._session
|
|
302
|
+
self._session = None
|
|
301
303
|
|
|
302
304
|
def get_trie_nodes(self) -> NoReturn:
|
|
303
305
|
raise RemoteTreeException(
|
|
@@ -305,8 +307,7 @@ class RemoteTree(AddressTree):
|
|
|
305
307
|
)
|
|
306
308
|
|
|
307
309
|
def installed_dictionary_version(self) -> str:
|
|
308
|
-
return
|
|
309
|
-
url=self.url,
|
|
310
|
+
return self.json_request(
|
|
310
311
|
method="jageocoder.installed_dictionary_version",
|
|
311
312
|
params={},
|
|
312
313
|
)
|
|
@@ -342,8 +343,7 @@ class RemoteTree(AddressTree):
|
|
|
342
343
|
[[[11460207:東京都(139.69178,35.68963)1(lasdec:130001/jisx0401:13)]>[12063502:多摩市(139.446366,35.636959)3(jisx0402:13224)]>[12065383:落合(139.427097,35.624877)5(None)]>[12065384:一丁目(139.427097,35.624877)6(None)]>[12065390:15番地(139.428969,35.625779)7(None)], '多摩市落合1-15-']]
|
|
343
344
|
""" # noqa: E501
|
|
344
345
|
self.address_nodes.update_server_signature()
|
|
345
|
-
rpc_result =
|
|
346
|
-
url=self.url,
|
|
346
|
+
rpc_result = self.json_request(
|
|
347
347
|
method="jageocoder.searchNode",
|
|
348
348
|
params={"query": query, "config": self.config},
|
|
349
349
|
)
|
|
@@ -384,11 +384,10 @@ class RemoteTree(AddressTree):
|
|
|
384
384
|
-----
|
|
385
385
|
- The result list contains up to 3 nodes.
|
|
386
386
|
- Each element is a dict type with the following structure:
|
|
387
|
-
{"candidate":AddressNode, "dist":float}
|
|
387
|
+
{"candidate":AddressNode, "dist":float}
|
|
388
388
|
"""
|
|
389
389
|
self.address_nodes.update_server_signature()
|
|
390
|
-
rpc_result =
|
|
391
|
-
url=self.url,
|
|
390
|
+
rpc_result = self.json_request(
|
|
392
391
|
method="jageocoder.reverse",
|
|
393
392
|
params={
|
|
394
393
|
"x": x,
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from abc import ABC
|
|
2
|
-
import copy
|
|
3
2
|
from logging import getLogger
|
|
4
3
|
import os
|
|
5
4
|
from typing import Iterable, List, Optional, Tuple
|
|
@@ -321,6 +320,7 @@ class Index(object):
|
|
|
321
320
|
node_table: AddressNodeTable = self._tree.address_nodes
|
|
322
321
|
|
|
323
322
|
max_id = node_table.count_records()
|
|
323
|
+
registered_coordinates = set()
|
|
324
324
|
|
|
325
325
|
logger.info("Building RTree for reverse geocoding...")
|
|
326
326
|
id = AddressNode.ROOT_NODE_ID
|
|
@@ -331,18 +331,69 @@ class Index(object):
|
|
|
331
331
|
prev_id = id
|
|
332
332
|
|
|
333
333
|
node = node_table.get_record(pos=id)
|
|
334
|
-
if node.level <= AddressLevel.WARD
|
|
334
|
+
if node.level <= AddressLevel.WARD:
|
|
335
|
+
registered_coordinates.clear()
|
|
335
336
|
id += 1
|
|
336
337
|
continue
|
|
337
338
|
|
|
338
|
-
if
|
|
339
|
-
node
|
|
339
|
+
if node.sibling_id == node.id + 1:
|
|
340
|
+
# The node has no child nodes
|
|
341
|
+
|
|
342
|
+
if not node.has_valid_coordinate_values():
|
|
343
|
+
id += 1
|
|
344
|
+
continue
|
|
345
|
+
|
|
346
|
+
key = (node.x, node.y)
|
|
347
|
+
if key in registered_coordinates:
|
|
348
|
+
id += 1
|
|
349
|
+
continue
|
|
340
350
|
|
|
341
|
-
if node.has_valid_coordinate_values():
|
|
342
351
|
file_idx.insert(
|
|
343
352
|
id=id,
|
|
344
353
|
coordinates=(node.x, node.y, node.x, node.y),
|
|
345
354
|
)
|
|
355
|
+
registered_coordinates.add(key)
|
|
356
|
+
id += 1
|
|
357
|
+
continue
|
|
358
|
+
|
|
359
|
+
# The node has 1 or more child nodes
|
|
360
|
+
if node.level == AddressLevel.BLOCK:
|
|
361
|
+
# Get BDR of child nodes
|
|
362
|
+
bdr = None
|
|
363
|
+
for child_id in range(node.id + 1, node.sibling_id):
|
|
364
|
+
child_node = node_table.get_record(child_id)
|
|
365
|
+
if not child_node.has_valid_coordinate_values():
|
|
366
|
+
continue
|
|
367
|
+
|
|
368
|
+
if bdr is None:
|
|
369
|
+
bdr = (child_node.x, child_node.y,
|
|
370
|
+
child_node.x, child_node.y)
|
|
371
|
+
else:
|
|
372
|
+
bdr = (
|
|
373
|
+
min(child_node.x, bdr[0]),
|
|
374
|
+
min(child_node.y, bdr[1]),
|
|
375
|
+
max(child_node.x, bdr[2]),
|
|
376
|
+
max(child_node.y, bdr[3]),
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
if bdr:
|
|
380
|
+
file_idx.insert(
|
|
381
|
+
id=id,
|
|
382
|
+
coordinates=bdr,
|
|
383
|
+
)
|
|
384
|
+
else:
|
|
385
|
+
# All child nodes have invalid coordinate values
|
|
386
|
+
key = (node.x, node.y)
|
|
387
|
+
if node.has_valid_coordinate_values() and \
|
|
388
|
+
key not in registered_coordinates:
|
|
389
|
+
file_idx.insert(
|
|
390
|
+
id=id,
|
|
391
|
+
coordinates=(node.x, node.y, node.x, node.y),
|
|
392
|
+
)
|
|
393
|
+
registered_coordinates.add(key)
|
|
394
|
+
|
|
395
|
+
id = node.sibling_id
|
|
396
|
+
continue
|
|
346
397
|
|
|
347
398
|
id += 1
|
|
348
399
|
|
|
@@ -377,7 +428,6 @@ class Index(object):
|
|
|
377
428
|
"""
|
|
378
429
|
node_table = self._tree.address_nodes
|
|
379
430
|
node = node_table.get_record(pos=node_table.count_records() // 2)
|
|
380
|
-
|
|
381
431
|
while True:
|
|
382
432
|
while node.level < AddressLevel.BLOCK:
|
|
383
433
|
node = node_table.get_record(pos=node.id + 1)
|
|
@@ -390,16 +440,26 @@ class Index(object):
|
|
|
390
440
|
|
|
391
441
|
node = node_table.get_record(pos=node.sibling_id)
|
|
392
442
|
|
|
393
|
-
results = tuple(self.idx.nearest((node.x, node.y, node.x, node.y),
|
|
394
|
-
|
|
443
|
+
results = tuple(self.idx.nearest((node.x, node.y, node.x, node.y), 1, objects=True))
|
|
444
|
+
if len(results) == 0:
|
|
445
|
+
return False
|
|
446
|
+
|
|
447
|
+
item = results[0]
|
|
448
|
+
target_node = node_table.get_record(item.id)
|
|
449
|
+
target_bbox = item.bbox
|
|
450
|
+
res = target_node.x >= target_bbox[0] \
|
|
451
|
+
and target_node.y >= target_bbox[1] \
|
|
452
|
+
and target_node.x <= target_bbox[2] \
|
|
453
|
+
and target_node.y <= target_bbox[3]
|
|
454
|
+
|
|
455
|
+
return res
|
|
395
456
|
|
|
396
457
|
def _sort_by_dist(
|
|
397
458
|
self,
|
|
398
459
|
lon: float,
|
|
399
460
|
lat: float,
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
) -> Tuple[List[NodeDist], dict]:
|
|
461
|
+
nodes: Iterable[AddressNode],
|
|
462
|
+
) -> List[NodeDist]:
|
|
403
463
|
"""
|
|
404
464
|
Sort nodes by real(projected) distance from the target point.
|
|
405
465
|
|
|
@@ -409,31 +469,24 @@ class Index(object):
|
|
|
409
469
|
The longitude of the target point.
|
|
410
470
|
lat: float
|
|
411
471
|
The latitude of the target point.
|
|
412
|
-
|
|
413
|
-
The list of node
|
|
472
|
+
nodes: Iterable[AddressNode]
|
|
473
|
+
The list of candidate node.
|
|
414
474
|
|
|
415
475
|
Returns
|
|
416
476
|
-------
|
|
417
|
-
|
|
418
|
-
The sorted list of (distance, address node)
|
|
477
|
+
List[NodeDist]
|
|
478
|
+
The sorted list of (distance, address node).
|
|
419
479
|
"""
|
|
420
|
-
node_map = node_map or {}
|
|
421
480
|
results = []
|
|
422
|
-
for
|
|
423
|
-
node = self._tree.get_node_by_id(node_id=node_id)
|
|
481
|
+
for node in nodes:
|
|
424
482
|
if not node.has_valid_coordinate_values():
|
|
425
|
-
|
|
483
|
+
continue
|
|
426
484
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
node_map[key].append(node)
|
|
430
|
-
else:
|
|
431
|
-
node_map[key] = [node]
|
|
432
|
-
dist = self.distance(node.x, node.y, lon, lat)
|
|
433
|
-
results.append(NodeDist(dist, node))
|
|
485
|
+
dist = self.distance(node.x, node.y, lon, lat)
|
|
486
|
+
results.append(NodeDist(dist, node))
|
|
434
487
|
|
|
435
488
|
results.sort(key=lambda x: x.dist)
|
|
436
|
-
return
|
|
489
|
+
return results
|
|
437
490
|
|
|
438
491
|
def nearest(
|
|
439
492
|
self,
|
|
@@ -463,117 +516,25 @@ class Index(object):
|
|
|
463
516
|
[{"candidate":AddressNode or dict, "dist":float}]
|
|
464
517
|
Returns the results of retrieval up to 3 nodes.
|
|
465
518
|
"""
|
|
466
|
-
|
|
467
|
-
def _remove_parent_nodes(
|
|
468
|
-
candidates: Iterable[NodeDist]
|
|
469
|
-
) -> List[NodeDist]:
|
|
470
|
-
ancestors = set()
|
|
471
|
-
max_level = 0
|
|
472
|
-
if len(candidates) == 0:
|
|
473
|
-
return []
|
|
474
|
-
|
|
475
|
-
nodes = []
|
|
476
|
-
for v in candidates:
|
|
477
|
-
dist, node = v.dist, v.node
|
|
478
|
-
if node.id in ancestors:
|
|
479
|
-
continue
|
|
480
|
-
|
|
481
|
-
if not node.has_valid_coordinate_values():
|
|
482
|
-
node = node.add_dummy_coordinates()
|
|
483
|
-
|
|
484
|
-
nodes.append(NodeDist(dist, node))
|
|
485
|
-
max_level = max(max_level, node.level)
|
|
486
|
-
|
|
487
|
-
# List ancestor nodes of registering node.
|
|
488
|
-
cur = node.parent
|
|
489
|
-
while cur is not None:
|
|
490
|
-
# nodes = [node for node in nodes if node.id != cur.id]
|
|
491
|
-
ancestors.add(cur.id)
|
|
492
|
-
cur = cur.parent
|
|
493
|
-
|
|
494
|
-
# Exclude ancestor nodes
|
|
495
|
-
nodes = [node for node in nodes if node.node.id not in ancestors]
|
|
496
|
-
return nodes
|
|
497
|
-
|
|
498
|
-
def _get_k_nearest_child_nodes(
|
|
499
|
-
aza_node_dists: List[NodeDist],
|
|
500
|
-
*,
|
|
501
|
-
candidates: Optional[List[NodeDist]] = None,
|
|
502
|
-
node_map: Optional[dict] = None,
|
|
503
|
-
k: Optional[int] = 20,
|
|
504
|
-
min_k: Optional[int] = 0,
|
|
505
|
-
max_dist: Optional[float] = 500.0,
|
|
506
|
-
) -> Tuple[List[NodeDist], dict]:
|
|
507
|
-
candidates = candidates or []
|
|
508
|
-
node_map = node_map or {}
|
|
509
|
-
for v in aza_node_dists:
|
|
510
|
-
dist, node = v.dist, v.node
|
|
511
|
-
child_id = node.id + 1
|
|
512
|
-
for child_id in range(node.id + 1, node.sibling_id):
|
|
513
|
-
child_node = self._tree.get_node_by_id(
|
|
514
|
-
node_id=child_id)
|
|
515
|
-
if child_node.level > level:
|
|
516
|
-
continue
|
|
517
|
-
|
|
518
|
-
if not child_node.has_valid_coordinate_values():
|
|
519
|
-
if child_node.level == level:
|
|
520
|
-
continue
|
|
521
|
-
|
|
522
|
-
child_node = child_node.add_dummy_coordinates()
|
|
523
|
-
if not child_node.has_valid_coordinate_values():
|
|
524
|
-
continue
|
|
525
|
-
|
|
526
|
-
key = (child_node.x, child_node.y)
|
|
527
|
-
if key in node_map:
|
|
528
|
-
# A node with the same coordinates are already registered
|
|
529
|
-
node_map[key].append(child_node)
|
|
530
|
-
continue
|
|
531
|
-
|
|
532
|
-
dist = self.distance(x, y, child_node.x, child_node.y)
|
|
533
|
-
i = len(candidates)
|
|
534
|
-
while i > 0:
|
|
535
|
-
if dist >= candidates[i - 1].dist:
|
|
536
|
-
break
|
|
537
|
-
|
|
538
|
-
i -= 1
|
|
539
|
-
|
|
540
|
-
if i < min_k or (i < k and dist <= max_dist):
|
|
541
|
-
candidates.insert(i, NodeDist(dist, child_node))
|
|
542
|
-
node_map[key] = [child_node]
|
|
543
|
-
n = len(candidates)
|
|
544
|
-
if n > k:
|
|
545
|
-
delnode = candidates[k].node
|
|
546
|
-
del candidates[k]
|
|
547
|
-
delkey = (delnode.x, delnode.y)
|
|
548
|
-
node_map[delkey].remove(delnode)
|
|
549
|
-
if len(node_map[delkey]) == 0:
|
|
550
|
-
del node_map[delkey]
|
|
551
|
-
|
|
552
|
-
elif n > min_k and candidates[min_k].dist > max_dist:
|
|
553
|
-
delnode = candidates[min_k].node
|
|
554
|
-
del candidates[min_k]
|
|
555
|
-
delkey = (delnode.x, delnode.y)
|
|
556
|
-
node_map[delkey].remove(delnode)
|
|
557
|
-
if len(node_map[delkey]) == 0:
|
|
558
|
-
del node_map[delkey]
|
|
559
|
-
|
|
560
|
-
return (candidates, node_map)
|
|
561
|
-
|
|
562
519
|
level = level or AddressLevel.AZA
|
|
563
520
|
|
|
564
521
|
# Retrieve top k-nearest nodes using the R-tree index.
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
candidates
|
|
573
|
-
|
|
574
|
-
|
|
522
|
+
# If the node registered in the index is an intermediate node,
|
|
523
|
+
# expand its leaf nodes.
|
|
524
|
+
candidates = []
|
|
525
|
+
nearests = self.idx.nearest((x, y, x, y), 20, objects=True)
|
|
526
|
+
for item in nearests:
|
|
527
|
+
node = self._tree.get_node_by_id(item.id)
|
|
528
|
+
if item.bbox[0] == item.bbox[2] and item.bbox[1] == item.bbox[3]:
|
|
529
|
+
candidates.append(node)
|
|
530
|
+
else:
|
|
531
|
+
for child_id in range(node.id + 1, node.sibling_id):
|
|
532
|
+
child_node = self._tree.get_node_by_id(child_id)
|
|
533
|
+
if child_node.sibling_id == child_id + 1 and \
|
|
534
|
+
child_node.has_valid_coordinate_values():
|
|
535
|
+
candidates.append(child_node)
|
|
575
536
|
|
|
576
|
-
|
|
537
|
+
node_dists = self._sort_by_dist(x, y, candidates)
|
|
577
538
|
|
|
578
539
|
# Select the 3 nodes that make the smallest triangle
|
|
579
540
|
# surrounding the target point
|
|
@@ -589,17 +550,6 @@ class Index(object):
|
|
|
589
550
|
else:
|
|
590
551
|
node_dists = DelaunayTriangle.select(x, y, node_dists)
|
|
591
552
|
|
|
592
|
-
# Restore nodes with the same coordinates
|
|
593
|
-
if node_map is not None:
|
|
594
|
-
_node_dists = []
|
|
595
|
-
for v in node_dists:
|
|
596
|
-
dist, node = v.dist, v.node
|
|
597
|
-
key = (node.x, node.y)
|
|
598
|
-
for n in node_map[key]:
|
|
599
|
-
_node_dists.append(NodeDist(dist, n))
|
|
600
|
-
|
|
601
|
-
node_dists = _node_dists
|
|
602
|
-
|
|
603
553
|
# Convert nodes to the dict format.
|
|
604
554
|
results = []
|
|
605
555
|
registered = set()
|
|
@@ -607,23 +557,14 @@ class Index(object):
|
|
|
607
557
|
dist, node = v.dist, v.node
|
|
608
558
|
while node.level > level:
|
|
609
559
|
node = node.parent
|
|
610
|
-
dist = None
|
|
611
|
-
if not node.has_valid_coordinate_values():
|
|
612
|
-
node.x, node.y = v.node.x, v.node.y
|
|
613
560
|
|
|
614
561
|
if node.id in registered:
|
|
615
562
|
continue
|
|
616
563
|
|
|
617
|
-
if dist is None:
|
|
618
|
-
dist = self.distance(x, y, node.x, node.y)
|
|
619
|
-
|
|
620
564
|
results.append({
|
|
621
565
|
"candidate": node.as_dict() if as_dict else node,
|
|
622
566
|
"dist": dist
|
|
623
567
|
})
|
|
624
568
|
registered.add(node.id)
|
|
625
569
|
|
|
626
|
-
# Sort by distance
|
|
627
|
-
results = sorted(results, key=lambda r: r['dist'])
|
|
628
|
-
|
|
629
570
|
return results
|
|
@@ -9,6 +9,7 @@ from typing import Any, Union, List, Set, Optional
|
|
|
9
9
|
|
|
10
10
|
from deprecated import deprecated
|
|
11
11
|
|
|
12
|
+
import jaconv
|
|
12
13
|
from jageocoder.address import AddressLevel
|
|
13
14
|
from jageocoder.aza_master import AzaMaster
|
|
14
15
|
from jageocoder.exceptions import AddressTreeException
|
|
@@ -849,7 +850,7 @@ class AddressTree(object):
|
|
|
849
850
|
key = index[0:offset]
|
|
850
851
|
rest_index = index[offset:]
|
|
851
852
|
for node_id in trie_node.nodes:
|
|
852
|
-
node = self.
|
|
853
|
+
node = self.get_node_by_id(node_id=node_id)
|
|
853
854
|
|
|
854
855
|
if not node.has_valid_coordinate_values() \
|
|
855
856
|
and self.get_config('require_coordinates'):
|
|
@@ -1160,7 +1161,7 @@ class AddressTree(object):
|
|
|
1160
1161
|
-----
|
|
1161
1162
|
- The result list contains up to 3 nodes.
|
|
1162
1163
|
- Each element is a dict type with the following structure:
|
|
1163
|
-
{"candidate":AddressNode, "dist":float}
|
|
1164
|
+
{"candidate":AddressNode, "dist":float}
|
|
1164
1165
|
"""
|
|
1165
1166
|
if self.reverse_index is None:
|
|
1166
1167
|
from jageocoder.rtree import Index
|
|
@@ -1168,6 +1169,15 @@ class AddressTree(object):
|
|
|
1168
1169
|
|
|
1169
1170
|
return self.reverse_index.nearest(x=x, y=y, level=level, as_dict=as_dict)
|
|
1170
1171
|
|
|
1172
|
+
@classmethod
|
|
1173
|
+
def _clean_numerical_string(cls, code: str) -> str:
|
|
1174
|
+
"""
|
|
1175
|
+
Clean numeric string.
|
|
1176
|
+
"""
|
|
1177
|
+
code = jaconv.zen2han(code, kana=False, ascii=False, digit=True)
|
|
1178
|
+
code = re.sub(r'\D', '', code)
|
|
1179
|
+
return code
|
|
1180
|
+
|
|
1171
1181
|
def search_by_machiaza_id(
|
|
1172
1182
|
self,
|
|
1173
1183
|
id: str
|
|
@@ -1194,6 +1204,7 @@ class AddressTree(object):
|
|
|
1194
1204
|
- Otherwise, it searches for address nodes whose machiaza-id matches "id"
|
|
1195
1205
|
from all municipalities. In this case, aza_id must be 7 characters.
|
|
1196
1206
|
"""
|
|
1207
|
+
id = self._clean_numerical_string(id)
|
|
1197
1208
|
if len(id) == 12:
|
|
1198
1209
|
# jisx0402(5digits) + aza_id(7digits)
|
|
1199
1210
|
citynode = self.search_by_citycode(code=id[0:5])
|
|
@@ -1247,6 +1258,7 @@ class AddressTree(object):
|
|
|
1247
1258
|
-----
|
|
1248
1259
|
- The "code" must be 7 characters.
|
|
1249
1260
|
"""
|
|
1261
|
+
code = self._clean_numerical_string(code)
|
|
1250
1262
|
if len(code) == 7:
|
|
1251
1263
|
# Postcode(7digits)
|
|
1252
1264
|
return self.search_nodes_by_codes(
|
|
@@ -1277,6 +1289,7 @@ class AddressTree(object):
|
|
|
1277
1289
|
- If "code" is 2 characters, the code is considered the JISX0401 code.
|
|
1278
1290
|
- If "code" is 6 characters, the code is considered the local-govenment code.
|
|
1279
1291
|
"""
|
|
1292
|
+
code = self._clean_numerical_string(code)
|
|
1280
1293
|
if len(code) == 2:
|
|
1281
1294
|
# jisx0401(2digits)
|
|
1282
1295
|
return self.search_nodes_by_codes(
|
|
@@ -1313,6 +1326,7 @@ class AddressTree(object):
|
|
|
1313
1326
|
- If "code" is 5 characters, the code is considered the JISX0402 code.
|
|
1314
1327
|
- If "code" is 6 characters, the code is considered the local-govenment code.
|
|
1315
1328
|
"""
|
|
1329
|
+
code = self._clean_numerical_string(code)
|
|
1316
1330
|
if len(code) == 5:
|
|
1317
1331
|
# jisx0402(5digits)
|
|
1318
1332
|
return self.search_nodes_by_codes(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|