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.
- package/LICENSE +15 -0
- package/inkscape/extensions/Attic/fix_guides.inx +17 -0
- package/inkscape/extensions/Attic/fix_guides.py +30 -0
- package/inkscape/extensions/Attic/fix_symbol_links.inx +17 -0
- package/inkscape/extensions/Attic/fix_symbol_links.py +71 -0
- package/inkscape/extensions/Attic/flatten_style.inx +17 -0
- package/inkscape/extensions/Attic/flatten_style.py +31 -0
- package/inkscape/extensions/Attic/load_markers.inx +17 -0
- package/inkscape/extensions/Attic/load_markers.py +117 -0
- package/inkscape/extensions/Attic/load_patterns.inx +17 -0
- package/inkscape/extensions/Attic/load_patterns.py +116 -0
- package/inkscape/extensions/Attic/load_symbols.inx +17 -0
- package/inkscape/extensions/Attic/load_symbols.py +164 -0
- package/inkscape/extensions/Attic/renumber_tree.inx +24 -0
- package/inkscape/extensions/Attic/renumber_tree.py +39 -0
- package/inkscape/extensions/Attic/repeat_path.inx +17 -0
- package/inkscape/extensions/Attic/repeat_path.py +58 -0
- package/inkscape/extensions/Attic/resolve_facility_links.inx +19 -0
- package/inkscape/extensions/Attic/resolve_facility_links.py +16 -0
- package/inkscape/extensions/Attic/sort_symbols.inx +17 -0
- package/inkscape/extensions/Attic/sort_symbols.py +48 -0
- package/inkscape/extensions/Attic/symbol_load.inx +17 -0
- package/inkscape/extensions/Attic/symbol_load.py +99 -0
- package/inkscape/extensions/Attic/tidy_tree.inx +22 -0
- package/inkscape/extensions/Attic/tidy_tree.py +178 -0
- package/inkscape/extensions/Attic/use_shop.inx +22 -0
- package/inkscape/extensions/Attic/use_shop.py +26 -0
- package/inkscape/extensions/README.md +34 -0
- package/inkscape/extensions/daijimaps/__init__.py +72 -0
- package/inkscape/extensions/daijimaps/address_tree.py +231 -0
- package/inkscape/extensions/daijimaps/common.py +49 -0
- package/inkscape/extensions/daijimaps/generate_addresses.py +41 -0
- package/inkscape/extensions/daijimaps/guards.py +47 -0
- package/inkscape/extensions/daijimaps/map_layer.py +47 -0
- package/inkscape/extensions/daijimaps/name.py +167 -0
- package/inkscape/extensions/daijimaps/renumber.py +38 -0
- package/inkscape/extensions/daijimaps/resolve_labels.py +65 -0
- package/inkscape/extensions/daijimaps/resolve_names.py +170 -0
- package/inkscape/extensions/daijimaps/save_addresses.py +274 -0
- package/inkscape/extensions/daijimaps/types.py +128 -0
- package/inkscape/extensions/daijimaps/visit_parents.py +37 -0
- package/inkscape/extensions/extract_labels.py +79 -0
- package/inkscape/extensions/fixup_floor_svg.py +28 -0
- package/inkscape/extensions/fixup_texts.inx +15 -0
- package/inkscape/extensions/fixup_texts.py +110 -0
- package/inkscape/extensions/fixup_tree.inx +15 -0
- package/inkscape/extensions/fixup_tree.py +17 -0
- package/inkscape/extensions/flatten_transform.inx +17 -0
- package/inkscape/extensions/flatten_transform.py +63 -0
- package/inkscape/extensions/import_labels.inx +20 -0
- package/inkscape/extensions/import_labels.py +52 -0
- package/inkscape/extensions/import_shops.inx +20 -0
- package/inkscape/extensions/import_shops.py +47 -0
- package/inkscape/extensions/install.sh +14 -0
- package/inkscape/extensions/load_shops.inx +20 -0
- package/inkscape/extensions/load_shops.py +86 -0
- package/inkscape/extensions/renumber_group.inx +15 -0
- package/inkscape/extensions/renumber_group.py +17 -0
- package/inkscape/extensions/resolve_labels.inx +20 -0
- package/inkscape/extensions/resolve_labels.py +142 -0
- package/inkscape/extensions/resolve_shops.inx +20 -0
- package/inkscape/extensions/resolve_shops.py +73 -0
- package/inkscape/extensions/unresolve_labels.inx +20 -0
- package/inkscape/extensions/unresolve_labels.py +53 -0
- package/inkscape/extensions/unresolve_shops.inx +20 -0
- package/inkscape/extensions/unresolve_shops.py +56 -0
- package/inkscape/templates/floors.svg +665 -0
- package/map-extract-floors.js +821 -0
- package/package.json +19 -0
- package/pyproject.toml +20 -0
- package/scripts/inkex-inkscape +14 -0
- package/scripts/inkex-python +14 -0
- package/scripts/inkex-setup +6 -0
- package/scripts/labels.sh +18 -0
- package/scripts/regen.py +77 -0
- 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]
|