svgmapviewer-tools-floors 0.0.1

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 (76) hide show
  1. package/LICENSE +15 -0
  2. package/inkscape/extensions/Attic/fix_guides.inx +17 -0
  3. package/inkscape/extensions/Attic/fix_guides.py +30 -0
  4. package/inkscape/extensions/Attic/fix_symbol_links.inx +17 -0
  5. package/inkscape/extensions/Attic/fix_symbol_links.py +71 -0
  6. package/inkscape/extensions/Attic/flatten_style.inx +17 -0
  7. package/inkscape/extensions/Attic/flatten_style.py +31 -0
  8. package/inkscape/extensions/Attic/load_markers.inx +17 -0
  9. package/inkscape/extensions/Attic/load_markers.py +117 -0
  10. package/inkscape/extensions/Attic/load_patterns.inx +17 -0
  11. package/inkscape/extensions/Attic/load_patterns.py +116 -0
  12. package/inkscape/extensions/Attic/load_symbols.inx +17 -0
  13. package/inkscape/extensions/Attic/load_symbols.py +164 -0
  14. package/inkscape/extensions/Attic/renumber_tree.inx +24 -0
  15. package/inkscape/extensions/Attic/renumber_tree.py +39 -0
  16. package/inkscape/extensions/Attic/repeat_path.inx +17 -0
  17. package/inkscape/extensions/Attic/repeat_path.py +58 -0
  18. package/inkscape/extensions/Attic/resolve_facility_links.inx +19 -0
  19. package/inkscape/extensions/Attic/resolve_facility_links.py +16 -0
  20. package/inkscape/extensions/Attic/sort_symbols.inx +17 -0
  21. package/inkscape/extensions/Attic/sort_symbols.py +48 -0
  22. package/inkscape/extensions/Attic/symbol_load.inx +17 -0
  23. package/inkscape/extensions/Attic/symbol_load.py +99 -0
  24. package/inkscape/extensions/Attic/tidy_tree.inx +22 -0
  25. package/inkscape/extensions/Attic/tidy_tree.py +178 -0
  26. package/inkscape/extensions/Attic/use_shop.inx +22 -0
  27. package/inkscape/extensions/Attic/use_shop.py +26 -0
  28. package/inkscape/extensions/README.md +34 -0
  29. package/inkscape/extensions/daijimaps/__init__.py +72 -0
  30. package/inkscape/extensions/daijimaps/address_tree.py +231 -0
  31. package/inkscape/extensions/daijimaps/common.py +49 -0
  32. package/inkscape/extensions/daijimaps/generate_addresses.py +41 -0
  33. package/inkscape/extensions/daijimaps/guards.py +47 -0
  34. package/inkscape/extensions/daijimaps/map_layer.py +47 -0
  35. package/inkscape/extensions/daijimaps/name.py +167 -0
  36. package/inkscape/extensions/daijimaps/renumber.py +38 -0
  37. package/inkscape/extensions/daijimaps/resolve_labels.py +65 -0
  38. package/inkscape/extensions/daijimaps/resolve_names.py +170 -0
  39. package/inkscape/extensions/daijimaps/save_addresses.py +274 -0
  40. package/inkscape/extensions/daijimaps/types.py +128 -0
  41. package/inkscape/extensions/daijimaps/visit_parents.py +37 -0
  42. package/inkscape/extensions/extract_labels.py +79 -0
  43. package/inkscape/extensions/fixup_floor_svg.py +28 -0
  44. package/inkscape/extensions/fixup_texts.inx +15 -0
  45. package/inkscape/extensions/fixup_texts.py +110 -0
  46. package/inkscape/extensions/fixup_tree.inx +15 -0
  47. package/inkscape/extensions/fixup_tree.py +17 -0
  48. package/inkscape/extensions/flatten_transform.inx +17 -0
  49. package/inkscape/extensions/flatten_transform.py +63 -0
  50. package/inkscape/extensions/import_labels.inx +20 -0
  51. package/inkscape/extensions/import_labels.py +52 -0
  52. package/inkscape/extensions/import_shops.inx +20 -0
  53. package/inkscape/extensions/import_shops.py +47 -0
  54. package/inkscape/extensions/install.sh +14 -0
  55. package/inkscape/extensions/load_shops.inx +20 -0
  56. package/inkscape/extensions/load_shops.py +86 -0
  57. package/inkscape/extensions/renumber_group.inx +15 -0
  58. package/inkscape/extensions/renumber_group.py +17 -0
  59. package/inkscape/extensions/resolve_labels.inx +20 -0
  60. package/inkscape/extensions/resolve_labels.py +142 -0
  61. package/inkscape/extensions/resolve_shops.inx +20 -0
  62. package/inkscape/extensions/resolve_shops.py +73 -0
  63. package/inkscape/extensions/unresolve_labels.inx +20 -0
  64. package/inkscape/extensions/unresolve_labels.py +53 -0
  65. package/inkscape/extensions/unresolve_shops.inx +20 -0
  66. package/inkscape/extensions/unresolve_shops.py +56 -0
  67. package/inkscape/templates/floors.svg +665 -0
  68. package/map-extract-floors.js +821 -0
  69. package/package.json +19 -0
  70. package/pyproject.toml +20 -0
  71. package/scripts/inkex-inkscape +14 -0
  72. package/scripts/inkex-python +14 -0
  73. package/scripts/inkex-setup +6 -0
  74. package/scripts/labels.sh +18 -0
  75. package/scripts/regen.py +77 -0
  76. package/scripts/regen.sh +24 -0
@@ -0,0 +1,65 @@
1
+ import inkex
2
+ import inkex.command
3
+
4
+ from .resolve_names import ResolveNames
5
+
6
+ # from .save_addresses import SaveAddresses
7
+ from .types import (
8
+ AddressNames,
9
+ )
10
+
11
+
12
+ class ResolveLabels(ResolveNames):
13
+ # - read (Names)
14
+ # - read (Unresolved Labels)
15
+ # - match them by name
16
+
17
+ _unresolved_labels: dict[str, inkex.TextElement] = {}
18
+ _resolved_labels: dict[str, inkex.TextElement] = {}
19
+
20
+ unresolved_labels: AddressNames = {}
21
+ resolved_labels: AddressNames = {}
22
+
23
+ def _resolve_labels(self) -> None:
24
+ self.msg("=== resolve labels: start")
25
+ self.msg("=== resolve labels: end")
26
+
27
+ def _read_unresolved_labels(self, node: inkex.Group) -> None:
28
+ for child in list(node):
29
+ if not isinstance(child, inkex.TextElement):
30
+ # XXX msg
31
+ continue
32
+ name = child.get("inkscape:label")
33
+ if not name:
34
+ self.msg(f"loading (Unresolved Labels): {child.label}: failed")
35
+ continue
36
+ if name in self._unresolved_labels:
37
+ self.msg(f"duplicate label: {name}")
38
+ continue
39
+ self._unresolved_labels[name] = child
40
+
41
+ def _read_resolved_labels(self, node: inkex.Group) -> None:
42
+ for child in list(node):
43
+ if not isinstance(child, inkex.TextElement):
44
+ # XXX msg
45
+ continue
46
+ if not child.label:
47
+ self.msg(f"loading (Labels): {child}: failed")
48
+ continue
49
+ name = child.label.split(" @ ")[0]
50
+ if name in self._resolved_labels:
51
+ self.msg(f"duplicate label: {name}")
52
+ continue
53
+ self._resolved_labels[name] = child
54
+
55
+ def _prepare_unresolved_labels_group(self, layer) -> None | inkex.Group:
56
+ unresolved_labels_group = self._find_or_make_group(layer, "(Unresolved Labels)")
57
+ if unresolved_labels_group is not None:
58
+ self._read_unresolved_labels(unresolved_labels_group)
59
+ return unresolved_labels_group
60
+
61
+ def _prepare_labels_group(self, layer) -> None | inkex.Group:
62
+ labels_group = self._find_or_make_group(layer, "(Labels)")
63
+ if labels_group is not None:
64
+ self._read_resolved_labels(labels_group)
65
+ return labels_group
@@ -0,0 +1,170 @@
1
+ import json
2
+ import os
3
+
4
+ import inkex
5
+ import inkex.command
6
+
7
+ from .common import a2astr, a2v, xy2v, V
8
+ from .name import read_name
9
+ from .save_addresses import SaveAddresses
10
+ from .types import (
11
+ AddressNames,
12
+ AddressString,
13
+ FloorsAddressesJson,
14
+ FloorsNamesJson,
15
+ NameAddresses,
16
+ TmpNameAddress,
17
+ TmpNameCoords,
18
+ )
19
+
20
+
21
+ class ResolveNames(SaveAddresses):
22
+ _resolved_names: NameAddresses = {}
23
+ _resolved_addresses: AddressNames = {}
24
+ _unresolved_names: NameAddresses = {}
25
+ _unresolved_addresses: AddressNames = {}
26
+ # _tmp_unresolved_name_coords: TmpNameCoords = {}
27
+ _tmp_resolved_names: TmpNameAddress = {}
28
+
29
+ def _exec_resolve(self) -> str:
30
+ exe: str = "%s/../resolve-addresses" % os.path.dirname(__file__)
31
+ return inkex.command.call(
32
+ exe,
33
+ self._layerPaths["addresses"],
34
+ self._layerPaths["tmpUnresolvedNames"],
35
+ self._layerPaths["tmpResolvedNames"],
36
+ )
37
+
38
+ def _read_names(self, node: inkex.Group) -> tuple[NameAddresses, AddressNames]:
39
+ name_addresses: NameAddresses = {}
40
+ address_names: AddressNames = {}
41
+ for child in list(node):
42
+ if not isinstance(child, inkex.TextElement):
43
+ # XXX msg
44
+ continue
45
+ shop = read_name(child)
46
+ if not shop:
47
+ self.msg(f"loading (Names): {child.label}: failed")
48
+ continue
49
+ (address, name, xy) = shop
50
+ # name -> (address, xy)
51
+ # address can be None
52
+ if name not in name_addresses:
53
+ name_addresses[name] = []
54
+ name_addresses[name].append((address, xy))
55
+ # address -> (name, xy)
56
+ # address must not be None
57
+ if address is None:
58
+ self.msg(f"skipping a resolved name without address: {name}")
59
+ continue
60
+ if address not in address_names:
61
+ address_names[address] = []
62
+ address_names[address].append((name, xy))
63
+
64
+ return (name_addresses, address_names)
65
+
66
+ def _read_resolved_names(self, node: inkex.Group) -> AddressNames:
67
+ (name_addresses, address_names) = self._read_names(node)
68
+ self._resolved_names = name_addresses
69
+ self._resolved_addresses = address_names
70
+ return address_names
71
+
72
+ def _read_unresolved_names(self, node: inkex.Group) -> NameAddresses:
73
+ (name_addresses, address_names) = self._read_names(node)
74
+ self._unresolved_names = name_addresses
75
+ self._unresolved_addresses = address_names
76
+ return name_addresses
77
+
78
+ def _load_tmp_resolved_names(self) -> None:
79
+ p = self._layerPaths["tmpResolvedNames"]
80
+ assert p is not None, "tmp resolved_names.json path is unspecified"
81
+ with open(p, mode="r", encoding="utf-8") as f:
82
+ # XXX
83
+ # XXX
84
+ # XXX
85
+ # XXX validate
86
+ j = json.load(f)
87
+ # XXX
88
+ # XXX
89
+ # XXX
90
+ self._tmp_resolved_names = j
91
+
92
+ def _get_tmp_unresolved_names(self) -> TmpNameCoords:
93
+ j: TmpNameCoords = {}
94
+ for name in self._unresolved_names:
95
+ xys: list[V] = list(map(a2v, self._unresolved_names[name]))
96
+ j[name] = xys
97
+ return j
98
+
99
+ def _get_floors_addresses(self) -> FloorsAddressesJson:
100
+ j: FloorsAddressesJson = {}
101
+ for astr in self._addresses:
102
+ ((x, y), _bb, _url) = self._addresses[astr]
103
+ j[astr] = xy2v(x, y)
104
+ return j
105
+
106
+ def _get_floors_names(self) -> FloorsNamesJson:
107
+ j: FloorsNamesJson = {}
108
+ for name in self._resolved_names:
109
+ aa = self._resolved_names[name]
110
+ xs: list[AddressString | None] = list(map(a2astr, aa))
111
+ j[name] = [x for x in xs if x is not None]
112
+ return j
113
+
114
+ def _save_resolved_names(self) -> None:
115
+ j: NameAddresses = self._resolved_names
116
+ p = self._layerPaths["resolvedNames"]
117
+ makedirsAndDump(p, j)
118
+
119
+ def _save_unresolved_names(self) -> None:
120
+ j: NameAddresses = self._unresolved_names
121
+ p = self._layerPaths["unresolvedNames"]
122
+ makedirsAndDump(p, j)
123
+
124
+ def _save_tmp_unresolved_names(self) -> None:
125
+ j: TmpNameCoords = self._get_tmp_unresolved_names()
126
+ p = self._layerPaths["tmpUnresolvedNames"]
127
+ makedirsAndDump(p, j)
128
+
129
+ def _save_floors_addresses(self) -> None:
130
+ j: FloorsAddressesJson = self._get_floors_addresses()
131
+ p = self._layerPaths["floorsAddresses"]
132
+ makedirsAndDump(p, j)
133
+
134
+ def _save_floors_names(self) -> None:
135
+ j: FloorsNamesJson = self._get_floors_names()
136
+ p = self._layerPaths["floorsNames"]
137
+ makedirsAndDump(p, j)
138
+
139
+ def _prepare_names_group(self, layer) -> inkex.Group | None:
140
+ names_group = self._find_or_make_group(layer, "(Names)")
141
+ self.msg(f"_process_addresses: names {names_group}")
142
+ if names_group is not None:
143
+ self._read_resolved_names(names_group)
144
+ else:
145
+ self._resolved_names = {}
146
+ return names_group
147
+
148
+ def _prepare_unresolved_names_group(self, layer) -> inkex.Group | None:
149
+ unresolved_names_group = self._find_or_make_group(layer, "(Unresolved Names)")
150
+ self.msg(f"_process_addresses: unresolved_names {unresolved_names_group}")
151
+ if unresolved_names_group is not None:
152
+ self._read_unresolved_names(unresolved_names_group)
153
+ else:
154
+ self._unresolved_names = {}
155
+ return unresolved_names_group
156
+
157
+ def _resolve_names(self):
158
+ self._save_tmp_unresolved_names()
159
+ self._exec_resolve()
160
+ self._load_tmp_resolved_names()
161
+
162
+
163
+ def makedirsAndDump(p: str, j: dict) -> None:
164
+ d = os.path.dirname(p)
165
+ os.makedirs(d, exist_ok=True)
166
+ with open(p, mode="w", encoding="utf-8") as f:
167
+ json.dump(j, f, indent=2, ensure_ascii=False)
168
+
169
+
170
+ __all__ = [ResolveNames]
@@ -0,0 +1,274 @@
1
+ import json
2
+ import os
3
+ import re
4
+ from typing import Any
5
+
6
+ import inkex
7
+
8
+ from .address_tree import AddressTree
9
+ from .common import xy2v
10
+ from .guards import isCircle, isEllipse, isGroup, isRectangle, isUse
11
+ from .types import AddressCoords, AddressString, Box, FacilitiesJson, FloorsInfoJson, V
12
+ from .visit_parents import Tree, Parents
13
+
14
+
15
+ class SaveAddresses(AddressTree):
16
+ def _prefix_fixup(self, prefix: str) -> str:
17
+ self.msg("=== _prefix_fixup@SaveAddresses")
18
+ # XXX
19
+ res = re.sub(r"-Content-", "-", prefix)
20
+ self.msg(f"=== _prefix_fixup@SaveAddresses: {res}")
21
+ return res
22
+
23
+ def _build_prefix(self, parents: list[inkex.Group]) -> str | None:
24
+ self.msg("=== _build_prefix@SaveAddresses")
25
+ plabels = [p.label for p in parents if isinstance(p.label, str)]
26
+ # all parents MUST have a label!
27
+ if None in plabels:
28
+ return None
29
+ prefix = "-".join(plabels)
30
+ sep = "-" if prefix != "" else ""
31
+ res = self._prefix_fixup(f"{self._global_prefix}{prefix}{sep}")
32
+ self.msg(f"=== _build_prefix@SaveAddresses: {res}")
33
+ return res
34
+
35
+ def _save_address2(self, astr: AddressString, px, py, bb, href) -> None:
36
+ self.msg(f"=== _save_address@SaveAddresses: {astr} @ {px},{py}")
37
+ p = (px, py)
38
+ self._addresses[astr] = (p, bb, href)
39
+ self._all_addresses[astr] = (p, bb, href)
40
+ if p not in self._points:
41
+ self._points[p] = []
42
+ self._points[p].append(astr)
43
+ if p not in self._all_points:
44
+ self._all_points[p] = []
45
+ self._all_points[p].append(astr)
46
+
47
+ def _save_address1(self, node: inkex.BaseElement, prefix: str, label: str) -> None:
48
+ astr = f"{prefix}{label}-{node.label}"
49
+ self.msg(f"=== _save_address1: {astr}")
50
+ bb: inkex.BoundingBox = node.shape_box()
51
+ c: inkex.ImmutableVector2d | None = None
52
+ p: inkex.ImmutableVector2d | None = None
53
+ tx = node.composed_transform()
54
+ if isUse(node):
55
+ x = float(node.get("x") or 0)
56
+ y = float(node.get("y") or 0)
57
+ c = inkex.ImmutableVector2d(x, y)
58
+ elif isCircle(node):
59
+ c = node.center
60
+ elif isEllipse(node):
61
+ c = node.center
62
+ if c is not None and bb is not None:
63
+ p = tx.apply_to_point(c)
64
+ hwh = (bb.width + bb.height) * 0.5
65
+ w = min(bb.width, hwh)
66
+ dx = (w * 0.8 - bb.width) * 0.5
67
+ bb = bb.resize(dx, 0)
68
+ if p is not None:
69
+ self._save_address2(astr, p.x, p.y, bb, node.href)
70
+
71
+ def _save_addresses(self, node: inkex.Group, prefix: str, label: str) -> None:
72
+ self.msg("=== _save_addresses@SaveAddresses")
73
+ for child in list(node):
74
+ if not isinstance(child, inkex.BaseElement):
75
+ continue
76
+ if label is None or child.label is None:
77
+ continue
78
+ self._save_address1(child, prefix, label)
79
+
80
+ def _visitor_node_branch_save_address(self, node: Tree, parents: Parents) -> None:
81
+ self.msg("=== _visitor_node_branch_save_address@SaveAddresses")
82
+ if node.label:
83
+ prefix = self._build_prefix(parents)
84
+ if prefix is not None:
85
+ self._save_addresses(node, prefix, node.label)
86
+
87
+ def _visitor_node_branch(self, node: Tree, parents: Parents) -> None:
88
+ self.msg("=== _visitor_node_branch@SaveAddresses")
89
+ self._visitor_node_branch_save_address(node, parents)
90
+
91
+ def _sort_children(self, node: inkex.Group) -> None:
92
+ self.msg("=== _sort_children@SaveAddresses")
93
+ children: dict[str, list[inkex.BaseElement]] = {}
94
+ for a in list(node):
95
+ if isinstance(a, inkex.Group) and a.label:
96
+ node.remove(a)
97
+ # a.label looks like: "Sov. @ A4F-Shops-1-3"
98
+ if a.label not in children:
99
+ children[a.label] = []
100
+ children[a.label].append(a)
101
+ # assume alphabetical order
102
+ labels = sorted(children.keys(), key=lambda label: str.lower(label))
103
+ for label in labels:
104
+ for a in children[label]:
105
+ node.append(a)
106
+
107
+ def _get_addresses_coords(self) -> AddressCoords:
108
+ j: AddressCoords = {}
109
+ for astr, ((x, y), _bb, _href) in self._addresses.items():
110
+ j[astr] = xy2v(x, y)
111
+ return j
112
+
113
+ def _get_origin(self) -> V | None:
114
+ origins: Any = self.svg.xpath(
115
+ '//*[@*[name()="inkscape:label"]="(Assets)"]/*[@*[name()="inkscape:label"]="(Origin)"]'
116
+ )
117
+ if not isinstance(origins, list) or len(origins) != 1:
118
+ self.msg("_get_origin: not found!")
119
+ return None
120
+ origin = list(origins)[0]
121
+ if not isCircle(origin):
122
+ self.msg("_get_origin: not circle!")
123
+ return None
124
+ c: inkex.ImmutableVector2d = origin.center
125
+ j: V = {
126
+ "x": c.x,
127
+ "y": c.y,
128
+ }
129
+ return j
130
+
131
+ def _get_viewbox(self, name="(ViewBox)") -> Box | None:
132
+ viewboxes: Any = self.svg.xpath(
133
+ f'//*[@*[name()="inkscape:label"]="(Assets)"]/*[@*[name()="inkscape:label"]="{name}"]'
134
+ )
135
+ if not isinstance(viewboxes, list) or len(viewboxes) != 1:
136
+ self.msg(f"_get_viewbox: not found! (name={name})")
137
+ return None
138
+ viewbox = list(viewboxes)[0]
139
+ if not isRectangle(viewbox):
140
+ self.msg(f"_get_viewbox: not rectangle! (name={name})")
141
+ return None
142
+ j: Box = {
143
+ "x": viewbox.left,
144
+ "y": viewbox.top,
145
+ "width": viewbox.width,
146
+ "height": viewbox.height,
147
+ }
148
+ return j
149
+
150
+ def _get_bbox(self):
151
+ return self._get_viewbox(name="(BoundingBox)")
152
+
153
+ def _get_floor(self, e: Any) -> tuple[str, FloorsInfoJson] | None:
154
+ if not isGroup(e):
155
+ self.msg(f"_get_floors: not group! ({e})")
156
+ return None
157
+ label = e.label
158
+ if label is None:
159
+ self.msg(f"_get_floors: no label! ({e})")
160
+ return None
161
+ # find 'Content'
162
+ subelems = e.xpath("./*[@*[name()='inkscape:label']='Content']")
163
+ if not isinstance(subelems, list) or len(subelems) != 1:
164
+ self.msg(f"_get_floors: not found! {subelems}")
165
+ return None
166
+ content = subelems[0]
167
+ if not isGroup(content):
168
+ self.msg(f"_get_floors: not group! ({content})")
169
+ return None
170
+ id = content.get_id()
171
+ return (
172
+ label,
173
+ {
174
+ "contentId": id,
175
+ },
176
+ )
177
+
178
+ def _get_floors(self) -> dict[str, FloorsInfoJson] | None:
179
+ elems: Any = self.svg.xpath("/*/*[@*[name()='inkscape:groupmode']='layer']")
180
+ if not isinstance(elems, list):
181
+ self.msg(f"_get_floors: not found! {elems}")
182
+ return None
183
+ layers: dict[str, FloorsInfoJson] = {}
184
+ for e in elems:
185
+ res = self._get_floor(e)
186
+ if res is None:
187
+ continue
188
+ (label, info) = res
189
+ layers[label] = info
190
+ return layers
191
+
192
+ def _save_addresses_coords(self, node):
193
+ j: AddressCoords = self._get_addresses_coords()
194
+ p = self._layerPaths["addresses"]
195
+ makedirsAndDump(p, j)
196
+
197
+ def _save_origin(self):
198
+ j: V | None = self._get_origin()
199
+ if j is None:
200
+ return
201
+ p = self._paths["origin"]
202
+ makedirsAndDump(p, dict(j))
203
+
204
+ def _save_viewbox(self):
205
+ j: Box | None = self._get_viewbox()
206
+ if j is None:
207
+ return
208
+ p = self._paths["viewbox"]
209
+ makedirsAndDump(p, dict(j))
210
+
211
+ def _save_bbox(self):
212
+ j: Box | None = self._get_bbox()
213
+ if j is None:
214
+ return
215
+ p = self._paths["bbox"]
216
+ makedirsAndDump(p, dict(j))
217
+
218
+ def _save_floors(self):
219
+ j: dict[str, FloorsInfoJson] | None = self._get_floors()
220
+ if j is None:
221
+ return
222
+ p = self._paths["floors"]
223
+ makedirsAndDump(p, dict(j))
224
+
225
+ def _post_collect_addresses(self, node):
226
+ self.msg("=== _post_collect_addresses@SaveAddresses")
227
+
228
+ self._save_addresses_coords(node)
229
+ self._save_origin()
230
+ self._save_viewbox()
231
+ self._save_bbox()
232
+ self._save_floors()
233
+
234
+ self.msg("=== _post_collect_addresses@SaveAddresses")
235
+
236
+ def _collect_links(self) -> None:
237
+ self.msg("=== _collect_links@SaveAddresses")
238
+ n = 1
239
+ for p in self._all_points:
240
+ if len(p) == 1:
241
+ continue
242
+ xs: list[AddressString] = [
243
+ a for a in self._all_points[p] if re.match("^.*-Facilities-.*$", a)
244
+ ]
245
+ if len(xs) <= 1:
246
+ continue
247
+ # XXX check kind (e.g. Elevator, Stairs)
248
+ # XXX don't hardcode
249
+ xxs: list[AddressString] = [
250
+ x for x in xs if re.match("^.*(Elevator|Stairs).*$", x)
251
+ ]
252
+ if len(xxs) <= 1:
253
+ continue
254
+ self.msg(f"links: {p}: {self._all_points[p]}")
255
+ self._links[str(n)] = xxs
256
+ n = n + 1
257
+
258
+ def _save_links(self) -> None:
259
+ self.msg("=== _save_links@SaveAddresses")
260
+ j: FacilitiesJson = {"biLinks": self._links}
261
+
262
+ p = self._paths["facilities"]
263
+ assert isinstance(p, str)
264
+ makedirsAndDump(p, dict(j))
265
+
266
+
267
+ def makedirsAndDump(p: str, j: dict) -> None:
268
+ d = os.path.dirname(p)
269
+ os.makedirs(d, exist_ok=True)
270
+ with open(p, mode="w", encoding="utf-8") as f:
271
+ json.dump(j, f, indent=2, ensure_ascii=False)
272
+
273
+
274
+ __all__ = [SaveAddresses]
@@ -0,0 +1,128 @@
1
+ import inkex
2
+ from typing import TypedDict
3
+
4
+
5
+ type Url = str
6
+
7
+ type AddressPosEntry = tuple[XY, inkex.BoundingBox, Url]
8
+ type AddressPos = dict[AddressString, AddressPosEntry]
9
+ type PosAddress = dict[XY, list[AddressString]]
10
+
11
+ type AddressString = str
12
+ type NameString = str
13
+ type XY = tuple[float, float]
14
+
15
+ type Address = tuple[AddressString | None, XY]
16
+ type Addresses = list[Address]
17
+ type NameAddresses = dict[NameString, Addresses]
18
+
19
+ type Name = tuple[NameString, XY]
20
+ type Names = list[Name]
21
+ type AddressNames = dict[AddressString, Names]
22
+
23
+
24
+ class V(TypedDict):
25
+ x: float
26
+ y: float
27
+
28
+
29
+ class Box(TypedDict):
30
+ x: float
31
+ y: float
32
+ width: float
33
+ height: float
34
+
35
+
36
+ type AddressCoords = dict[AddressString, V] # addresses.json
37
+
38
+ type TmpNameCoords = dict[NameString, list[V]] # tmp_unresolved_addresses.json
39
+ type TmpNameAddress = dict[
40
+ NameString, list[AddressString]
41
+ ] # tmp_resolved_addresses.json
42
+
43
+
44
+ # XXX
45
+ type LinkID = str
46
+ type Links = dict[LinkID, list[AddressString]]
47
+
48
+
49
+ # XXX
50
+ class FacilitiesJson(TypedDict):
51
+ biLinks: Links
52
+
53
+
54
+ #
55
+ # JSON for svgmapviewer floors
56
+ #
57
+ type FloorsAddressesJson = dict[AddressString, V] # floors-1F-addresses.json
58
+ type FloorsNamesJson = dict[NameString, list[AddressString]] # floors-1F-names.json
59
+
60
+
61
+ # XXX
62
+ class FloorsInfoJson(TypedDict):
63
+ contentId: str
64
+
65
+
66
+ class TspanInfoJson(TypedDict):
67
+ attrs: dict[str, str]
68
+ text: str
69
+
70
+
71
+ class TextInfoJson(TypedDict):
72
+ attrs: dict[str, str]
73
+ children: list[TspanInfoJson]
74
+
75
+
76
+ #
77
+ # File paths
78
+ #
79
+ class JsonGlobalPaths(TypedDict):
80
+ facilities: str
81
+ floors: str
82
+ origin: str
83
+ viewbox: str
84
+ bbox: str
85
+
86
+
87
+ #
88
+ # Per-layer file paths
89
+ #
90
+ class JsonLayerPaths(TypedDict):
91
+ addresses: str
92
+ unresolvedNames: str
93
+ resolvedNames: str
94
+ tmpUnresolvedNames: str
95
+ tmpResolvedNames: str
96
+ floorsAddresses: str
97
+ floorsNames: str
98
+
99
+
100
+ __all__ = [
101
+ # .address_tree
102
+ Address,
103
+ AddressNames,
104
+ AddressPos,
105
+ AddressPosEntry,
106
+ AddressString,
107
+ Addresses,
108
+ Box,
109
+ FacilitiesJson,
110
+ FloorsInfoJson,
111
+ JsonGlobalPaths,
112
+ JsonLayerPaths,
113
+ FloorsAddressesJson,
114
+ FloorsNamesJson,
115
+ Links,
116
+ Name,
117
+ NameAddresses,
118
+ NameString,
119
+ Names,
120
+ AddressCoords,
121
+ TextInfoJson,
122
+ TmpNameCoords,
123
+ TmpNameAddress,
124
+ TspanInfoJson,
125
+ Url,
126
+ XY,
127
+ V,
128
+ ]
@@ -0,0 +1,37 @@
1
+ import typing
2
+ from typing import Callable
3
+ import inkex
4
+
5
+
6
+ type Cont = typing.Literal[0]
7
+ type Skip = typing.Literal[1]
8
+ type Exit = typing.Literal[2]
9
+ type Visit = Cont | Skip | Exit
10
+
11
+ type Tree = inkex.Group
12
+ type Parents = list[inkex.Group]
13
+ type Visitor = Callable[[Tree, Parents], Visit]
14
+
15
+
16
+ CONT = 0
17
+ SKIP = 1
18
+ EXIT = 2
19
+
20
+
21
+ def _visit_parents_inner(tree: Tree, parents: Parents, visitor: Visitor) -> None:
22
+ res = visitor(tree, parents)
23
+ if res == SKIP:
24
+ return
25
+ parents.append(tree)
26
+ for child in list(tree):
27
+ if not isinstance(child, inkex.Group):
28
+ continue
29
+ _visit_parents_inner(child, parents, visitor)
30
+ parents.pop()
31
+
32
+
33
+ def _visit_parents(tree: Tree, visitor: Visitor) -> None:
34
+ _visit_parents_inner(tree, [], visitor)
35
+
36
+
37
+ __all__ = [Parents, Tree, Visit, Visitor, CONT, SKIP, EXIT, _visit_parents]