jageocoder 2.1.6__tar.gz → 2.1.7.dev2__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 (21) hide show
  1. {jageocoder-2.1.6 → jageocoder-2.1.7.dev2}/PKG-INFO +11 -4
  2. {jageocoder-2.1.6 → jageocoder-2.1.7.dev2}/README.md +9 -3
  3. {jageocoder-2.1.6 → jageocoder-2.1.7.dev2}/jageocoder/__init__.py +8 -2
  4. {jageocoder-2.1.6 → jageocoder-2.1.7.dev2}/jageocoder/address.py +43 -0
  5. {jageocoder-2.1.6 → jageocoder-2.1.7.dev2}/jageocoder/module.py +114 -0
  6. {jageocoder-2.1.6 → jageocoder-2.1.7.dev2}/jageocoder/node.py +39 -3
  7. {jageocoder-2.1.6 → jageocoder-2.1.7.dev2}/jageocoder/remote.py +93 -49
  8. {jageocoder-2.1.6 → jageocoder-2.1.7.dev2}/jageocoder/rtree.py +25 -5
  9. {jageocoder-2.1.6 → jageocoder-2.1.7.dev2}/jageocoder/tree.py +167 -427
  10. {jageocoder-2.1.6 → jageocoder-2.1.7.dev2}/pyproject.toml +2 -1
  11. jageocoder-2.1.6/jageocoder/aliases.json +0 -72
  12. {jageocoder-2.1.6 → jageocoder-2.1.7.dev2}/LICENSE +0 -0
  13. {jageocoder-2.1.6 → jageocoder-2.1.7.dev2}/jageocoder/__main__.py +0 -0
  14. {jageocoder-2.1.6 → jageocoder-2.1.7.dev2}/jageocoder/aza_master.py +0 -0
  15. {jageocoder-2.1.6 → jageocoder-2.1.7.dev2}/jageocoder/dataset.py +0 -0
  16. {jageocoder-2.1.6 → jageocoder-2.1.7.dev2}/jageocoder/exceptions.py +0 -0
  17. {jageocoder-2.1.6 → jageocoder-2.1.7.dev2}/jageocoder/itaiji.py +0 -0
  18. {jageocoder-2.1.6 → jageocoder-2.1.7.dev2}/jageocoder/itaiji_dic.json +0 -0
  19. {jageocoder-2.1.6 → jageocoder-2.1.7.dev2}/jageocoder/result.py +0 -0
  20. {jageocoder-2.1.6 → jageocoder-2.1.7.dev2}/jageocoder/strlib.py +0 -0
  21. {jageocoder-2.1.6 → jageocoder-2.1.7.dev2}/jageocoder/trie.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: jageocoder
3
- Version: 2.1.6
3
+ Version: 2.1.7.dev2
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
@@ -29,6 +29,7 @@ Requires-Dist: jaconv (>=0.3.4,<0.4.0)
29
29
  Requires-Dist: marisa-trie (>=0.7.8)
30
30
  Requires-Dist: portabletab (>=0.3.3)
31
31
  Requires-Dist: pycapnp
32
+ Requires-Dist: requests
32
33
  Requires-Dist: rtree (>=1.0.0,<2.0.0)
33
34
  Requires-Dist: tqdm (>=4.00.0,<5.0.0)
34
35
  Requires-Dist: urllib3 (>=2.0.6)
@@ -44,7 +45,7 @@ This is a Python port of the Japanese-address geocoder `DAMS` used in CSIS at th
44
45
 
45
46
  # Getting Started
46
47
 
47
- This package provides address-geocoding functionality for Python programs. The basic usage is to specify a dictionary with `init()` then call `search()` to get geocoding results.
48
+ This package provides address-geocoding and reverse-geocoding functionality for Python programs. The basic usage is to specify a dictionary with `init()` then call `search()` to get geocoding results.
48
49
 
49
50
  ```python
50
51
  >>> import jageocoder
@@ -69,11 +70,11 @@ To use Jageocoder, you need to install the "Dictionary Database" on the same mac
69
70
 
70
71
  ### Install Dictionary Database
71
72
 
72
- Large amounts of data can be processed at high speed when a dictionary database is installed. A database covering addresses in Japan requires 30 GB or more of storage.
73
+ When a dictionary database is installed, large amounts of data can be processed at high speed. A database covering addresses in Japan requires 25 GB or more of storage.
73
74
 
74
75
  - Download an address database file compatible with that version from [here](https://www.info-proto.com/static/jageocoder/latest/v2/)
75
76
 
76
- wget https://www.info-proto.com/static/jageocoder/latest/v2/jukyo_all_v21.zip
77
+ jageocoder download-dictionary https://www.info-proto.com/static/jageocoder/latest/v2/jukyo_all_v21.zip
77
78
 
78
79
  - Install the dictionary with `install-dictionary` command
79
80
 
@@ -299,6 +300,12 @@ the block number (○番) contained therein.
299
300
  ['10番', '11番', '1番', '2番', '3番', '4番', '5番', '6番', '7番', '8番', '9番']
300
301
  ```
301
302
 
303
+ # For developers
304
+
305
+ ## Documentation
306
+
307
+ Tutorials and references are [here](https://jageocoder.readthedocs.io/ja/latest/).
308
+
302
309
  ## Create your own dictionary
303
310
 
304
311
  Consider using [jageocoder-converter](https://github.com/t-sagara/jageocoder-converter).
@@ -6,7 +6,7 @@ This is a Python port of the Japanese-address geocoder `DAMS` used in CSIS at th
6
6
 
7
7
  # Getting Started
8
8
 
9
- This package provides address-geocoding functionality for Python programs. The basic usage is to specify a dictionary with `init()` then call `search()` to get geocoding results.
9
+ This package provides address-geocoding and reverse-geocoding functionality for Python programs. The basic usage is to specify a dictionary with `init()` then call `search()` to get geocoding results.
10
10
 
11
11
  ```python
12
12
  >>> import jageocoder
@@ -31,11 +31,11 @@ To use Jageocoder, you need to install the "Dictionary Database" on the same mac
31
31
 
32
32
  ### Install Dictionary Database
33
33
 
34
- Large amounts of data can be processed at high speed when a dictionary database is installed. A database covering addresses in Japan requires 30 GB or more of storage.
34
+ When a dictionary database is installed, large amounts of data can be processed at high speed. A database covering addresses in Japan requires 25 GB or more of storage.
35
35
 
36
36
  - Download an address database file compatible with that version from [here](https://www.info-proto.com/static/jageocoder/latest/v2/)
37
37
 
38
- wget https://www.info-proto.com/static/jageocoder/latest/v2/jukyo_all_v21.zip
38
+ jageocoder download-dictionary https://www.info-proto.com/static/jageocoder/latest/v2/jukyo_all_v21.zip
39
39
 
40
40
  - Install the dictionary with `install-dictionary` command
41
41
 
@@ -261,6 +261,12 @@ the block number (○番) contained therein.
261
261
  ['10番', '11番', '1番', '2番', '3番', '4番', '5番', '6番', '7番', '8番', '9番']
262
262
  ```
263
263
 
264
+ # For developers
265
+
266
+ ## Documentation
267
+
268
+ Tutorials and references are [here](https://jageocoder.readthedocs.io/ja/latest/).
269
+
264
270
  ## Create your own dictionary
265
271
 
266
272
  Consider using [jageocoder-converter](https://github.com/t-sagara/jageocoder-converter).
@@ -19,7 +19,7 @@ running the following steps.
19
19
  >>> jageocoder.searchNode('<Japanese-address>')
20
20
  """
21
21
 
22
- __version__ = '2.1.6' # The package version
22
+ __version__ = '2.1.7.dev2' # The package version
23
23
  __dictionary_version__ = '20230927' # Compatible dictionary version
24
24
  __author__ = 'Takeshi Sagara <sagara@info-proto.com>'
25
25
 
@@ -37,6 +37,10 @@ __all__ = [
37
37
  'create_trie_index',
38
38
  'search',
39
39
  'searchNode',
40
+ 'search_by_machiaza_id',
41
+ 'search_by_postcode',
42
+ 'search_by_citycode',
43
+ 'search_by_prefcode',
40
44
  'reverse',
41
45
  'version',
42
46
  'dictionary_version',
@@ -48,5 +52,7 @@ from jageocoder.module import init, free, is_initialized, \
48
52
  get_db_dir, set_search_config, get_search_config, \
49
53
  get_module_tree, download_dictionary, install_dictionary, \
50
54
  uninstall_dictionary, create_trie_index, \
51
- search, searchNode, reverse, version, dictionary_version, \
55
+ search, searchNode, search_by_machiaza_id, search_by_postcode, \
56
+ search_by_citycode, search_by_prefcode, reverse, \
57
+ version, dictionary_version, \
52
58
  installed_dictionary_version, installed_dictionary_readme # noqa: F401
@@ -30,6 +30,49 @@ class AddressLevel(object):
30
30
  BLOCK = 7
31
31
  BLD = 8
32
32
 
33
+ @classmethod
34
+ def levelname(cls, level: int) -> str:
35
+ """
36
+ Returns the Japanese notation of the address level.
37
+
38
+ Parameters
39
+ ----------
40
+ level: int
41
+ The address level.
42
+
43
+ Returns
44
+ -------
45
+ str
46
+ """
47
+ if level == cls.PREF:
48
+ return "都道府県"
49
+
50
+ if level == cls.COUNTY:
51
+ return "郡"
52
+
53
+ if level == cls.CITY:
54
+ return "市町村・特別区"
55
+
56
+ if level == cls.WARD:
57
+ return "政令市の区"
58
+
59
+ if level == cls.OAZA:
60
+ return "町域・大字"
61
+
62
+ if level == cls.AZA:
63
+ return "丁目・小字"
64
+
65
+ if level == cls.BLOCK:
66
+ return "街区・道路・地番"
67
+
68
+ if level == cls.BLD:
69
+ return "建物・枝番"
70
+
71
+ if level == cls.UNDEFINED:
72
+ return "未定義"
73
+
74
+ return "不明"
75
+
33
76
  @classmethod
34
77
  def guess(cls, name, parent, trigger):
35
78
  """
@@ -482,6 +482,120 @@ def reverse(
482
482
  return _tree.reverse(x, y, level, as_dict)
483
483
 
484
484
 
485
+ def search_by_machiaza_id(
486
+ id: str
487
+ ) -> list:
488
+ """
489
+ Finds the corresponding address nodes from the "machiaza-id" of
490
+ the address base registry.
491
+
492
+ Parameters
493
+ ----------
494
+ id: str
495
+ Machiaza-id.
496
+
497
+ Returns
498
+ -------
499
+ List[AddressNode]
500
+
501
+ Notes
502
+ -----
503
+ - If "id" is 12 characters, the first 5 characters are considered the JISX0402 code.
504
+ - If "id" is 13 characters, the first 6 characters are considered the lg-code.
505
+ - In either of the above cases, search for the address node whose machiaza-id
506
+ matches the rest 7 characters in the corresponding municipality.
507
+ - Otherwise, it searches for address nodes whose machiaza-id matches "id"
508
+ from all municipalities. In this case, aza_id must be 7 characters.
509
+ """
510
+ if not is_initialized():
511
+ raise JageocoderError("Not initialized. Call 'init()' first.")
512
+
513
+ global _tree
514
+ return _tree.search_by_machiaza_id(id)
515
+
516
+
517
+ def search_by_postcode(
518
+ code: str
519
+ ) -> list:
520
+ """
521
+ Finds the corresponding address node from a postcode.
522
+
523
+ Parameters
524
+ ----------
525
+ code: str
526
+ The postal code as defined by the Japan Post.
527
+
528
+ Returns
529
+ -------
530
+ List[AddressNode]
531
+
532
+ Notes
533
+ -----
534
+ - The "code" must be 7 characters.
535
+ """
536
+ if not is_initialized():
537
+ raise JageocoderError("Not initialized. Call 'init()' first.")
538
+
539
+ global _tree
540
+ return _tree.search_by_postcode(code)
541
+
542
+
543
+ def search_by_prefcode(
544
+ code: str
545
+ ) -> list:
546
+ """
547
+ Finds the corresponding address nodes from the JISX0401 code
548
+ or the prefacture's local-government code.
549
+
550
+ Parameters
551
+ ----------
552
+ code: str
553
+ Prefacture code as defined in JISX0401, of local government code defined by MIC.
554
+
555
+ Returns
556
+ -------
557
+ List[AddressNode]
558
+
559
+ Notes
560
+ -----
561
+ - If "code" is 2 characters, the code is considered the JISX0401 code.
562
+ - If "code" is 6 characters, the code is considered the local-govenment code.
563
+ """
564
+ if not is_initialized():
565
+ raise JageocoderError("Not initialized. Call 'init()' first.")
566
+
567
+ global _tree
568
+ return _tree.search_by_prefcode(code)
569
+
570
+
571
+ def search_by_citycode(
572
+ code: str
573
+ ) -> list:
574
+ """
575
+ Finds the corresponding address nodes from the JISX0402 code
576
+ or the local-government code.
577
+
578
+ Parameters
579
+ ----------
580
+ code: str
581
+ City code as defined in JISX0402, of local government code defined by MIC.
582
+
583
+ Returns
584
+ -------
585
+ List[AddressNode]
586
+
587
+ Notes
588
+ -----
589
+ - If "code" is 5 characters, the code is considered the JISX0402 code.
590
+ - If "code" is 6 characters, the code is considered the local-govenment code.
591
+ """
592
+ if not is_initialized():
593
+ raise JageocoderError("Not initialized. Call 'init()' first.")
594
+
595
+ global _tree
596
+ return _tree.search_by_citycode(code)
597
+
598
+
485
599
  def create_trie_index() -> None:
486
600
  """
487
601
  Create the TRIE index from the database file.
@@ -212,10 +212,16 @@ class AddressNode(object):
212
212
  def dataset(self):
213
213
  """
214
214
  Get dataset record.
215
-
216
215
  """
217
216
  return self.table.datasets.get(id=self.priority)
218
217
 
218
+ @property
219
+ def levelname(self) -> str:
220
+ """
221
+ Get level by name.
222
+ """
223
+ return AddressLevel.levelname(self.level)
224
+
219
225
  @classmethod
220
226
  def from_record(cls, record) -> AddressNode:
221
227
  """
@@ -1252,6 +1258,28 @@ class AddressNode(object):
1252
1258
  }
1253
1259
  }
1254
1260
 
1261
+ def to_json(self):
1262
+ """
1263
+ Convert node to JSONable dict for data transfer.
1264
+
1265
+ Notes
1266
+ -----
1267
+ - Different from the 'as_dict' method, this method includes
1268
+ all attributes in the database, such as 'parent_id'.
1269
+ """
1270
+ return {
1271
+ "id": self.id,
1272
+ "name": self.name,
1273
+ "name_index": self.name_index,
1274
+ "x": self.x,
1275
+ "y": self.y,
1276
+ "level": self.level,
1277
+ "priority": self.priority,
1278
+ "note": self.note,
1279
+ "parent_id": self.parent_id,
1280
+ "sibling_id": self.sibling_id,
1281
+ }
1282
+
1255
1283
  def get_fullname(
1256
1284
  self,
1257
1285
  delimiter: Optional[str] = None,
@@ -1300,7 +1328,7 @@ class AddressNode(object):
1300
1328
 
1301
1329
  return nodes
1302
1330
 
1303
- def get_nodes_by_level(self):
1331
+ def get_nodes_by_level(self) -> List[AddressNode | None]:
1304
1332
  """
1305
1333
  The method returns an array of this node and its upper nodes.
1306
1334
  The Nth node of the array contains the node corresponding
@@ -1508,6 +1536,7 @@ class AddressNode(object):
1508
1536
  def get_aza_names(
1509
1537
  self,
1510
1538
  tree: Optional[AddressTree] = None,
1539
+ levelname: Optional[bool] = False,
1511
1540
  ) -> list:
1512
1541
  """
1513
1542
  Returns representation of Aza node containing this node.
@@ -1516,6 +1545,8 @@ class AddressNode(object):
1516
1545
  ----------
1517
1546
  tree: AddressTree, optional
1518
1547
  The tree containing this node.
1548
+ levelname: bool, optional
1549
+ If true, Returns the address level by name, not by number.
1519
1550
 
1520
1551
  Returns
1521
1552
  -------
@@ -1527,7 +1558,12 @@ class AddressNode(object):
1527
1558
  """
1528
1559
  aza_record = self.get_aza_record(tree)
1529
1560
  if aza_record:
1530
- return json.loads(aza_record.names)
1561
+ results = json.loads(aza_record.names)
1562
+ if levelname:
1563
+ for i in range(len(results)):
1564
+ results[i][0] = AddressLevel.levelname(results[i][0])
1565
+
1566
+ return results
1531
1567
 
1532
1568
  return []
1533
1569
 
@@ -1,17 +1,19 @@
1
1
  from functools import lru_cache
2
2
  import json
3
+ from logging import getLogger
3
4
  import os
4
5
  import requests
5
- from typing import Any, List, Optional, Union
6
+ from typing import Any, List, NoReturn, Optional, Union
6
7
  import uuid
7
8
 
8
9
  from jageocoder.address import AddressLevel
9
10
  from jageocoder.exceptions import RemoteTreeException
10
11
  from jageocoder.node import AddressNode
11
12
  from jageocoder.result import Result
12
- from jageocoder.tree import LRU
13
+ from jageocoder.tree import AddressTree, LRU
13
14
 
14
15
 
16
+ logger = getLogger(__name__)
15
17
  _session = None
16
18
 
17
19
 
@@ -28,8 +30,13 @@ def _json_request(
28
30
  "id": str(uuid.uuid4()),
29
31
  }
30
32
  if _session is None:
33
+ logger.debug("Start a new HTTP session with the remote tree.")
31
34
  _session = requests.Session()
32
35
 
36
+ logger.debug(
37
+ "Send JSON-RPC request ---\n" +
38
+ json.dumps(payload, indent=2, ensure_ascii=False)
39
+ )
33
40
  response = _session.post(
34
41
  url=url,
35
42
  data=json.dumps(payload),
@@ -39,6 +46,11 @@ def _json_request(
39
46
  if "error" in response:
40
47
  raise RemoteTreeException(response["error"])
41
48
 
49
+ logger.debug(
50
+ "Receive JSON-RPC response ---\n" +
51
+ json.dumps(response["result"], indent=2, ensure_ascii=False)
52
+ )
53
+
42
54
  return response["result"]
43
55
 
44
56
 
@@ -72,6 +84,7 @@ class RemoteNodeTable(object):
72
84
  self.datasets = RemoteDataset(url=url)
73
85
  self.cache = LRU()
74
86
  self.server_signature = None
87
+ self.mode = 'r' # Always read only
75
88
 
76
89
  def update_server_signature(self) -> str:
77
90
  """
@@ -97,7 +110,7 @@ class RemoteNodeTable(object):
97
110
 
98
111
  def get_record(self, pos: int) -> AddressNode:
99
112
  """
100
- Get the record at the specified position
113
+ Get the record at the specified position from the remote server
101
114
  and convert it to AddressNode object.
102
115
 
103
116
  Parameters
@@ -123,8 +136,56 @@ class RemoteNodeTable(object):
123
136
  self.cache[pos] = node
124
137
  return node
125
138
 
139
+ def search_records_on(
140
+ self,
141
+ attr: str,
142
+ value: str,
143
+ funcname: str = "get") -> list:
144
+ """
145
+ Search value from the table on the specified attribute on the remote server.
146
+
147
+ Paramters
148
+ ---------
149
+ attr: str
150
+ The name of target attribute.
151
+ value: str
152
+ The target value.
153
+ funcname: str
154
+ The name of search method.
155
+ - "get" searches for records that exactly match the value.
156
+ - "prefixes" searches for records that contained in the value.
157
+ - "keys" searches for records that containing the value.
158
+
159
+ Returns
160
+ -------
161
+ List[Record]
162
+ List of records.
163
+
164
+ Notes
165
+ -----
166
+ - TRIE index must be created on the column before searching.
167
+ - The TRIE index file will be automatically opened if it exists.
168
+ """
169
+ rpc_result = _json_request(
170
+ url=self.url,
171
+ method="node.search_records_on",
172
+ params={
173
+ "attr": attr,
174
+ "value": value,
175
+ "funcname": funcname,
176
+ "server": self.server_signature
177
+ },
178
+ )
179
+ nodes = []
180
+ for record in rpc_result:
181
+ node = AddressNode(**record)
182
+ node.table = self
183
+ nodes.append(node)
184
+
185
+ return nodes
186
+
126
187
 
127
- class RemoteTree(object):
188
+ class RemoteTree(AddressTree):
128
189
  """
129
190
  The proxy class for remote server's address-tree structure.
130
191
 
@@ -206,58 +267,18 @@ class RemoteTree(object):
206
267
  the new address is retrieved automatically.
207
268
  """
208
269
  for k, v in kwargs.items():
270
+ self.validate_config(key=k, value=v)
209
271
  self.config[k] = v
210
272
 
211
- def get_config(
212
- self,
213
- keys: Union[str, List[str], None] = None
214
- ) -> dict:
215
- """
216
- Get configurable parameter(s).
217
-
218
- Parameters
219
- ----------
220
- keys: str, List[str], optional
221
- If a name of parameter is specified, return its value.
222
- Otherwise, a dict of specified key and its value pairs
223
- will be returned.
224
-
225
- Returns
226
- -------
227
- Any, or dict.
228
- """
229
- if keys is None:
230
- return self.config
231
-
232
- if isinstance(keys, str):
233
- return self.config.get(keys)
234
-
235
- results = {}
236
- for key in keys:
237
- if key in self.config:
238
- results[key] = self.config[key]
239
-
240
- return results
241
-
242
273
  def _close(self) -> None:
243
274
  if _session:
244
275
  del _session
245
276
  _session = None
246
277
 
247
- def get_node_by_id(self, node_id: int) -> AddressNode:
248
- """
249
- Get the full node information by its id.
250
-
251
- Parameters
252
- ----------
253
- node_id: int
254
- The target node id.
255
-
256
- Return
257
- ------
258
- AddressNode
259
- """
260
- return self.address_nodes.get_record(node_id)
278
+ def get_trie_nodes(self) -> NoReturn:
279
+ raise RemoteTreeException(
280
+ "This method is not available for RemoteTree."
281
+ )
261
282
 
262
283
  def installed_dictionary_version(self) -> str:
263
284
  return _json_request(
@@ -266,6 +287,13 @@ class RemoteTree(object):
266
287
  params={},
267
288
  )
268
289
 
290
+ def search_by_trie(
291
+ self, *args, **kwargs
292
+ ) -> NoReturn:
293
+ raise RemoteTreeException(
294
+ "This method is not available for RemoteTree."
295
+ )
296
+
269
297
  def searchNode(self, query: str) -> List[Result]:
270
298
  """
271
299
  Searches for address nodes corresponding to an address notation
@@ -352,3 +380,19 @@ class RemoteTree(object):
352
380
  results.append(r)
353
381
 
354
382
  return results
383
+
384
+ def search_by_machiaza_id(self, id: str) -> List[AddressNode]:
385
+ self.address_nodes.update_server_signature()
386
+ return super().search_by_machiaza_id(id)
387
+
388
+ def search_by_postcode(self, code: str) -> List[AddressNode]:
389
+ self.address_nodes.update_server_signature()
390
+ return super().search_by_postcode(code)
391
+
392
+ def search_by_prefcode(self, code: str) -> List[AddressNode]:
393
+ self.address_nodes.update_server_signature()
394
+ return super().search_by_prefcode(code)
395
+
396
+ def search_by_citycode(self, code: str) -> List[AddressNode]:
397
+ self.address_nodes.update_server_signature()
398
+ return super().search_by_citycode(code)
@@ -324,8 +324,10 @@ class Index(object):
324
324
  id += 1
325
325
  continue
326
326
  elif not node.has_valid_coordinate_values():
327
- id += 1
328
- continue
327
+ node = node.add_dummy_coordinates()
328
+ if not node.has_valid_coordinate_values():
329
+ id += 1
330
+ continue
329
331
 
330
332
  file_idx.insert(
331
333
  id=id,
@@ -442,6 +444,9 @@ class Index(object):
442
444
  if node.id in ancestors:
443
445
  continue
444
446
 
447
+ if not node.has_valid_coordinate_values():
448
+ node = node.add_dummy_coordinates()
449
+
445
450
  nodes.append(node)
446
451
  max_level = max(max_level, node.level)
447
452
  # Ancestor nodes of registering node are excluded.
@@ -462,8 +467,10 @@ class Index(object):
462
467
  child_id = child_node.parent.sibling_id
463
468
  continue
464
469
  elif not child_node.has_valid_coordinate_values():
465
- child_id += 1
466
- continue
470
+ child_node = child_node.add_dummy_coordinates()
471
+ if not child_node.has_valid_coordinate_values():
472
+ child_id += 1
473
+ continue
467
474
 
468
475
  local_idx.insert(
469
476
  id=child_id,
@@ -478,6 +485,9 @@ class Index(object):
478
485
  if node.id in ancestors:
479
486
  continue
480
487
 
488
+ if not node.has_valid_coordinate_values():
489
+ node = node.add_dummy_coordinates()
490
+
481
491
  nodes.append(node)
482
492
  # Ancestor nodes of registering node are excluded.
483
493
  cur = node.parent
@@ -488,7 +498,17 @@ class Index(object):
488
498
 
489
499
  # Select the 3 nodes that make the smallest triangle
490
500
  # surrounding the target point
491
- nodes = DelaunayTriangle.select(x, y, nodes)
501
+ if len(nodes) == 0:
502
+ return []
503
+
504
+ if self.distance(x, y, nodes[0].x, nodes[0].y) < 1.0e-02:
505
+ # If the distance between the nearest point and the search point is
506
+ # less than 1 cm, it returns three points in order of distance.
507
+ # This is because the nearest point may not be included in
508
+ # the search results due to a calculation error.
509
+ nodes = nodes[0:3]
510
+ else:
511
+ nodes = DelaunayTriangle.select(x, y, nodes)
492
512
 
493
513
  # Convert nodes to the dict format.
494
514
  results = []