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.
Files changed (20) hide show
  1. {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/PKG-INFO +1 -1
  2. {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/__init__.py +1 -1
  3. {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/module.py +15 -6
  4. {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/node.py +11 -0
  5. {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/remote.py +65 -66
  6. {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/rtree.py +95 -154
  7. {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/tree.py +16 -2
  8. {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/pyproject.toml +1 -1
  9. {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/LICENSE +0 -0
  10. {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/README.md +0 -0
  11. {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/__main__.py +0 -0
  12. {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/address.py +0 -0
  13. {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/aza_master.py +0 -0
  14. {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/dataset.py +0 -0
  15. {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/exceptions.py +0 -0
  16. {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/itaiji.py +0 -0
  17. {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/itaiji_dic.json +0 -0
  18. {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/result.py +0 -0
  19. {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/strlib.py +0 -0
  20. {jageocoder-2.1.7.dev3 → jageocoder-2.1.7.post1}/jageocoder/trie.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: jageocoder
3
- Version: 2.1.7.dev3
3
+ Version: 2.1.7.post1
4
4
  Summary: A Japanese-address geocoder for Python.
5
5
  Home-page: https://github.com/t-sagara/jageocoder/
6
6
  License: The MIT License
@@ -19,7 +19,7 @@ running the following steps.
19
19
  >>> jageocoder.searchNode('<Japanese-address>')
20
20
  """
21
21
 
22
- __version__ = '2.1.7.dev3' # The package version
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
- _url = os.environ.get('JAGEOCODER_SERVER_URL')
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 functools import lru_cache
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, Union
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, url: str) -> None:
60
- self.url = url
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 = _json_request(
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, url: str) -> None:
83
- self.url = url
84
- self.datasets = RemoteDataset(url=url)
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 = _json_request(
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 = _json_request(
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 = _json_request(
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
- nodes = self.search_records_on(attr, value)
208
- ids = [node.id for node in nodes]
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(url)
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 _json_request(
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 = _json_request(
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 = _json_request(
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 or node.level > AddressLevel.BLOCK:
334
+ if node.level <= AddressLevel.WARD:
335
+ registered_coordinates.clear()
335
336
  id += 1
336
337
  continue
337
338
 
338
- if not node.has_valid_coordinate_values():
339
- node = node.add_dummy_coordinates()
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), 20))
394
- return len(results) > 0 and node.id in results
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
- id_list: Iterable[int],
401
- node_map: Optional[dict] = None,
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
- id_list: Iterable[int]
413
- The list of node-id.
472
+ nodes: Iterable[AddressNode]
473
+ The list of candidate node.
414
474
 
415
475
  Returns
416
476
  -------
417
- Tuple[List[NodeDist], dict]
418
- The sorted list of (distance, address node) and a node map.
477
+ List[NodeDist]
478
+ The sorted list of (distance, address node).
419
479
  """
420
- node_map = node_map or {}
421
480
  results = []
422
- for node_id in set(id_list):
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
- node = node.add_dummy_coordinates()
483
+ continue
426
484
 
427
- key = (node.x, node.y)
428
- if key in node_map:
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 (results, node_map)
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
- nearests = self.idx.nearest((x, y, x, y), 20)
566
- node_dists, node_map = self._sort_by_dist(x, y, nearests)
567
- node_dists = _remove_parent_nodes(node_dists)
568
-
569
- if level > AddressLevel.BLOCK:
570
- candidates, node_map = _get_k_nearest_child_nodes(
571
- node_dists,
572
- candidates=copy.copy(node_dists),
573
- node_map=node_map,
574
- min_k=1)
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
- node_dists = _remove_parent_nodes(candidates)
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.get_address_node(id=node_id)
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(
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "jageocoder"
3
- version = "2.1.7.dev3"
3
+ version = "2.1.7.post1"
4
4
  description = "A Japanese-address geocoder for Python."
5
5
  authors = ["Takeshi Sagara <sagara@info-proto.com>"]
6
6
  repository = "https://github.com/t-sagara/jageocoder/"