diff --git a/AGENTS.md b/AGENTS.md
index 62c311b..afc33ba 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -30,6 +30,7 @@ web-standalone/ Self-contained map tool (no DB)
changelog.json "What's new" entries, newest first; every user-visible change adds one
server.py stdlib-only HTTP server + proxy for upstream feeds (mc-radar, meshcore.io, PDOK, mc-spamdetector.nl)
spamdetector.py parses a spam-detector attack page into Step 1 clues (pure logic, tested)
+ bag3d.py 3D BAG: RD conversion, footprints, the tallest roof within reach of a node (pure logic, tested)
tests/ pytest for the pure logic; fixtures are trimmed copies of real upstream pages
tools/ accuracy.mjs (estimator error against known targets), its fixture, build-fixture.py
Dockerfile Alpine, non-root, stdlib-only
@@ -179,7 +180,7 @@ new hardcoded hex values.
### Don't hammer upstream feeds per-node
-The proxied upstream APIs (mc-radar, map.meshcore.io, PDOK, mc-spamdetector.nl) are third-party
+The proxied upstream APIs (mc-radar, map.meshcore.io, PDOK, mc-spamdetector.nl, 3D BAG) are third-party
and rate-limitable; mc-radar answers 429 after a few hundred proven-link fetches in a day.
Batch/cache lookups (as the existing code does, including the in-memory cache in `server.py`
for the spam detector) rather than firing one request per node/prefix.
diff --git a/README.md b/README.md
index 13bc38e..1599d5c 100644
--- a/README.md
+++ b/README.md
@@ -58,6 +58,7 @@ control in front if you do; the proxy has no auth of its own.
| map.meshcore.io | Global node feed (fallback). |
| PDOK AHN | Terrain/surface elevation (NL). |
| mc-spamdetector.nl | Incident list, and an incident's entry hops as Step 1 clues. |
+| 3D BAG (api.3dbag.nl) | The tallest building within 50 m of an observer, for its antenna height in the line-of-sight check (NL). |
Loaded by the page directly, not through the proxy: OpenFreeMap vector tiles (the Liberty basemap, no key) and AWS Terrain Tiles (relief in the 3D view, for looking at; line of sight is measured on AHN).
diff --git a/web-standalone/.dockerignore b/web-standalone/.dockerignore
index cb174c1..9913164 100644
--- a/web-standalone/.dockerignore
+++ b/web-standalone/.dockerignore
@@ -1,5 +1,6 @@
*
!server.py
!spamdetector.py
+!bag3d.py
!index.html
!changelog.json
diff --git a/web-standalone/Dockerfile b/web-standalone/Dockerfile
index 61141b7..2c88333 100644
--- a/web-standalone/Dockerfile
+++ b/web-standalone/Dockerfile
@@ -7,7 +7,7 @@ LABEL org.opencontainers.image.source="https://github.com/khagele/meshcore-trian
org.opencontainers.image.description="MeshCore triangulator (web-standalone): static UI + stdlib proxy to mc-radar/meshcore/PDOK"
WORKDIR /app
-COPY server.py spamdetector.py index.html changelog.json ./
+COPY server.py spamdetector.py bag3d.py index.html changelog.json ./
# Inside the container we must bind all interfaces; restrict exposure when
# publishing the port instead (e.g. -p 127.0.0.1:8000:8000).
diff --git a/web-standalone/bag3d.py b/web-standalone/bag3d.py
new file mode 100644
index 0000000..1ad0e31
--- /dev/null
+++ b/web-standalone/bag3d.py
@@ -0,0 +1,196 @@
+"""3D BAG lookups for the triangulator (#74).
+
+A repeater on a roof has its antenna at roof height plus a mast, not at the
+12 m the estimator assumed for every node. 3D BAG (TU Delft, CC BY 4.0)
+publishes per-building footprints with roof and ground heights. This module
+turns the responses of its OGC API Features endpoint into "the tallest
+building within reach of this point, how high its roof is above the ground,
+and how far away it is". Advertised positions are metres off and a mast can
+stand next to the building it serves, so the tallest building within
+50 m is the antenna's structure rather than the footprint under the point.
+
+Pure logic, stdlib only, so it is unit-testable (tests/test_bag3d.py).
+server.py fetches the payload and serves the result under /proxy/3dbag/.
+
+The API takes bounding boxes in EPSG:7415 (RD New + NAP), so lat/lon is
+converted with the usual polynomial approximation, good to about 25 cm.
+"""
+import math
+
+# Schreutelkops-Adema approximation, WGS84 -> RD New.
+_RD_X0, _RD_Y0 = 155000.0, 463000.0
+_LAT0, _LON0 = 52.15517440, 5.38720621
+
+_RD_X_TERMS = [(0, 1, 190094.945), (1, 1, -11832.228), (2, 1, -114.221), (0, 3, -32.391),
+ (1, 0, -0.705), (3, 1, -2.340), (1, 3, -0.608), (0, 2, -0.008), (2, 3, 0.148)]
+_RD_Y_TERMS = [(1, 0, 309056.544), (0, 2, 3638.893), (2, 0, 73.077), (1, 2, -157.984),
+ (3, 0, 59.788), (0, 1, 0.433), (2, 2, -6.439), (1, 1, -0.032), (0, 4, 0.092),
+ (1, 4, -0.054)]
+_WGS_LAT_TERMS = [(0, 1, 3235.65389), (2, 0, -32.58297), (0, 2, -0.24750), (2, 1, -0.84978),
+ (0, 3, -0.06550), (2, 2, -0.01709), (1, 0, -0.00738), (4, 0, 0.00530),
+ (2, 3, -0.00039), (4, 1, 0.00033), (1, 1, -0.00012)]
+_WGS_LON_TERMS = [(1, 0, 5260.52916), (1, 1, 105.94684), (1, 2, 2.45656), (3, 0, -0.81885),
+ (1, 3, 0.05594), (3, 1, -0.05607), (0, 1, 0.01199), (3, 2, -0.00256),
+ (1, 4, 0.00128), (0, 2, 0.00022), (2, 0, -0.00022), (5, 0, 0.00026)]
+
+
+def wgs84_to_rd(lat, lon):
+ """RD New x, y in metres for a WGS84 position."""
+ dlat = 0.36 * (lat - _LAT0)
+ dlon = 0.36 * (lon - _LON0)
+ x = _RD_X0 + sum(c * dlat ** p * dlon ** q for p, q, c in _RD_X_TERMS)
+ y = _RD_Y0 + sum(c * dlat ** p * dlon ** q for p, q, c in _RD_Y_TERMS)
+ return x, y
+
+
+def rd_to_wgs84(x, y):
+ """WGS84 lat, lon for an RD New position. Used by the tests to build a
+ lat/lon inside a known footprint; the app only goes the other way."""
+ dx = (x - _RD_X0) * 1e-5
+ dy = (y - _RD_Y0) * 1e-5
+ lat = _LAT0 + sum(c * dx ** p * dy ** q for p, q, c in _WGS_LAT_TERMS) / 3600.0
+ lon = _LON0 + sum(c * dx ** p * dy ** q for p, q, c in _WGS_LON_TERMS) / 3600.0
+ return lat, lon
+
+
+def bbox_around(lat, lon, radius_m):
+ """An RD bbox string for the API, radius_m either side of the point."""
+ x, y = wgs84_to_rd(lat, lon)
+ return "%.0f,%.0f,%.0f,%.0f" % (x - radius_m, y - radius_m, x + radius_m, y + radius_m)
+
+
+def _transform(payload, feature):
+ meta = payload.get("metadata") or {}
+ transform = meta.get("transform") or feature.get("transform") or {}
+ scale = transform.get("scale") or [1, 1, 1]
+ translate = transform.get("translate") or [0, 0, 0]
+ return scale, translate
+
+
+def buildings(payload):
+ """Every Building in a response: id, roof height above ground, the raw
+ heights, and its footprint rings in RD metres (outer ring first per
+ surface, inner rings are holes)."""
+ out = []
+ for feature in payload.get("features") or []:
+ scale, translate = _transform(payload, feature)
+ vertices = feature.get("vertices") or []
+
+ def xy(index):
+ v = vertices[index]
+ return (v[0] * scale[0] + translate[0], v[1] * scale[1] + translate[1])
+
+ for object_id, obj in (feature.get("CityObjects") or {}).items():
+ if obj.get("type") != "Building":
+ continue
+ attrs = obj.get("attributes") or {}
+ roof = attrs.get("b3_h_dak_max")
+ ground = attrs.get("b3_h_maaiveld")
+ surfaces = []
+ for geometry in obj.get("geometry") or []:
+ if str(geometry.get("lod")) != "0":
+ continue
+ for surface in geometry.get("boundaries") or []:
+ rings = [[xy(i) for i in ring] for ring in surface if ring]
+ if rings:
+ surfaces.append(rings)
+ out.append({
+ "id": object_id,
+ "roof_m": (roof - ground) if isinstance(roof, (int, float)) and isinstance(ground, (int, float)) else None,
+ "h_dak_max": roof,
+ "h_maaiveld": ground,
+ "h_nok": attrs.get("b3_h_nok"),
+ "surfaces": surfaces,
+ })
+ return out
+
+
+def point_in_ring(x, y, ring):
+ """Ray casting; a point on the edge counts as inside."""
+ inside = False
+ n = len(ring)
+ for i in range(n):
+ x1, y1 = ring[i]
+ x2, y2 = ring[(i + 1) % n]
+ if (y1 > y) != (y2 > y):
+ cross = (x2 - x1) * (y - y1) / (y2 - y1) + x1
+ if x <= cross:
+ inside = not inside
+ return inside
+
+
+def point_in_surface(x, y, rings):
+ if not rings or not point_in_ring(x, y, rings[0]):
+ return False
+ return not any(point_in_ring(x, y, hole) for hole in rings[1:])
+
+
+def building_at(payload, lat, lon):
+ """The building whose footprint contains the point, or None."""
+ x, y = wgs84_to_rd(lat, lon)
+ for building in buildings(payload):
+ if any(point_in_surface(x, y, rings) for rings in building["surfaces"]):
+ return building
+ return None
+
+
+def _segment_distance(px, py, ax, ay, bx, by):
+ dx, dy = bx - ax, by - ay
+ if dx == 0 and dy == 0:
+ return math.hypot(px - ax, py - ay)
+ t = max(0.0, min(1.0, ((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy)))
+ return math.hypot(px - (ax + t * dx), py - (ay + t * dy))
+
+
+def footprint_distance(x, y, surfaces):
+ """Metres from the point to the nearest footprint edge, 0 inside."""
+ best = None
+ for rings in surfaces:
+ if point_in_surface(x, y, rings):
+ return 0.0
+ outer = rings[0] if rings else []
+ n = len(outer)
+ for i in range(n):
+ ax, ay = outer[i]
+ bx, by = outer[(i + 1) % n]
+ d = _segment_distance(x, y, ax, ay, bx, by)
+ if best is None or d < best:
+ best = d
+ return best
+
+
+def tallest_near(payloads, lat, lon, radius_m):
+ """The tallest building whose footprint comes within radius_m of the
+ point, over one or more response pages. Ties go to the nearer one."""
+ if isinstance(payloads, dict):
+ payloads = [payloads]
+ x, y = wgs84_to_rd(lat, lon)
+ best = None
+ total = 0
+ for payload in payloads:
+ for building in buildings(payload):
+ total += 1
+ if building["roof_m"] is None or not building["surfaces"]:
+ continue
+ distance = footprint_distance(x, y, building["surfaces"])
+ if distance is None or distance > radius_m:
+ continue
+ key = (round(building["roof_m"], 1), -distance)
+ if best is None or key > best[0]:
+ best = (key, building, distance)
+ return best, total
+
+
+def roof_for_point(payloads, lat, lon, radius_m=50.0):
+ """What the page asks for: the roof height above ground of the tallest
+ building within radius_m, how far its footprint is, or nulls."""
+ best, total = tallest_near(payloads, lat, lon, radius_m)
+ if not best:
+ return {"roof_m": None, "building": None, "distance_m": None, "buildings": total}
+ _, building, distance = best
+ return {
+ "roof_m": round(building["roof_m"], 1),
+ "building": building["id"],
+ "distance_m": round(distance, 1),
+ "buildings": total,
+ }
diff --git a/web-standalone/changelog.json b/web-standalone/changelog.json
index 732746b..1304e30 100644
--- a/web-standalone/changelog.json
+++ b/web-standalone/changelog.json
@@ -1,4 +1,10 @@
[
+ {
+ "id": "2026-09-07-antenna-height-from-the-roof",
+ "date": "2026-09-07",
+ "title": "A repeater on a roof gets its real antenna height",
+ "body": "The line-of-sight check assumed every observer's antenna at 12 m. When a cluster is locked the tool now looks up, in 3D BAG, the tallest building within 50 m of each observer; those get its roof plus a 3 m mast, the others keep 12 m. The cluster status says how many, and an observer's popup shows its antenna height and how far that roof is. Netherlands only, like the terrain check."
+ },
{
"id": "2026-09-07-fresh-visit",
"date": "2026-09-07",
diff --git a/web-standalone/index.html b/web-standalone/index.html
index f7ac6fb..2084fe8 100644
--- a/web-standalone/index.html
+++ b/web-standalone/index.html
@@ -1737,8 +1737,50 @@
Unknown Node Estimate
const panelCluster = document.getElementById("panel-cluster");
const panelTerrain = document.getElementById("panel-terrain");
+ // Assumed when no building stands within ROOF_SEARCH_M of the node. Half
+ // of the fixture's observers stand within 12 m of one (32 of 60 sampled
+ // against 3D BAG), roof median 10.2 m above ground, p90 25 m. Advertised
+ // positions are metres off and a mast can stand next to the building it
+ // serves, so the tallest building within 50 m counts, and its roof plus
+ // a mast replaces this constant (#74).
const REPEATER_ANTENNA_HEIGHT_M = 12;
+ const ROOFTOP_MAST_M = 3;
+ const ROOF_SEARCH_M = 50;
const TARGET_ANTENNA_HEIGHT_M = 1.5;
+
+ // Antenna height above the ground for an observer: its roof plus a mast
+ // when 3D BAG put it on a building, the flat constant otherwise.
+ function observerAntennaHeightM(node) {
+ return Number.isFinite(node.roofHeightM) ? node.roofHeightM + ROOFTOP_MAST_M : REPEATER_ANTENNA_HEIGHT_M;
+ }
+
+ // One 3D BAG lookup per node, through server.py, which caches by
+ // position. Sets node.roofHeightM (metres above ground) and
+ // node.roofDistanceM when a building stands within ROOF_SEARCH_M; leaves
+ // them unset otherwise or when the lookup fails. Counts are returned for
+ // the status line.
+ async function fetchRoofHeights(nodes) {
+ let onRoof = 0;
+ let answered = 0;
+ await Promise.all(nodes.map(async (node) => {
+ if (!Number.isFinite(node.lat) || !Number.isFinite(node.lon)) return;
+ try {
+ const response = await fetch(`/proxy/3dbag/roof?lat=${node.lat.toFixed(5)}&lon=${node.lon.toFixed(5)}`);
+ if (!response.ok) return;
+ const data = await response.json();
+ answered += 1;
+ if (Number.isFinite(data.roof_m) && data.roof_m > 0) {
+ node.roofHeightM = data.roof_m;
+ node.roofDistanceM = Number.isFinite(data.distance_m) ? data.distance_m : null;
+ node.roofBuilding = data.building;
+ onRoof += 1;
+ }
+ } catch (error) {
+ // A failed lookup leaves the constant in place; the status line says how many answered.
+ }
+ }));
+ return { onRoof, answered, asked: nodes.length };
+ }
// Swept against the accuracy fixture (#53, #69): 2 km to 20 km moves median
// error 1.9 -> 2.0 km and p90 9.0 -> 9.1 km. This constant is inert. Error
// is set by observer geometry, not by assumed coverage size.
@@ -2905,6 +2947,10 @@ Unknown Node Estimate
if (moved) {
node.moved = true;
delete node.provenRadiusKm;
+ // The roof belonged to the position it left.
+ delete node.roofHeightM;
+ delete node.roofDistanceM;
+ delete node.roofBuilding;
} else {
delete node.moved;
}
@@ -3361,7 +3407,7 @@ Unknown Node Estimate
fetchAhnSurfaceElevation(to.lat, to.lon),
Promise.all(interior.map((point) => fetchAhnLayerValue(point.lat, point.lon, "dsm_05m", surfaceCache)))
]);
- const startHeight = fromGround + REPEATER_ANTENNA_HEIGHT_M;
+ const startHeight = fromGround + observerAntennaHeightM(from);
const endHeight = targetGround + TARGET_ANTENNA_HEIGHT_M;
const pathM = distanceKm * 1000;
let worstLossDb = 0;
@@ -4193,6 +4239,13 @@ Unknown Node Estimate
if (runId !== discoveryRunId) {
return;
}
+ // Roof heights for the same nodes the link budget covers (#74). The
+ // cluster copies are what the pools are built from below, so the value
+ // travels with the node from here on.
+ const roofs = await fetchRoofHeights(fetchTargets);
+ if (runId !== discoveryRunId) {
+ return;
+ }
const supportNodes = Array.from(supportNodeMap.values()).slice(0, 40);
const observerPoolMap = new Map();
clusterNodes.forEach((node) => {
@@ -4285,7 +4338,11 @@ Unknown Node Estimate
radiusNotes.push(`${observersWithoutReceiveQuorum} observer${observersWithoutReceiveQuorum === 1 ? "" : "s"} had under ${RECEIVE_QUORUM} inbound links, so used all directions`);
}
const implausibleNote = radiusNotes.length ? ` Coverage radii: ${radiusNotes.join("; ")}.` : "";
- clusterStatus.textContent = `Cluster near ${centroid.lat.toFixed(3)}, ${centroid.lon.toFixed(3)}: ${uniquePrefixCount(dedupedNodes)} observers active.${hullNote}${dedupNote}${linkCapNote}${implausibleNote}`;
+ // Which observers got a roof height, and which stay on the constant.
+ const roofNote = roofs.answered
+ ? ` Antenna heights: ${roofs.onRoof} of ${roofs.answered} observers have a building within ${ROOF_SEARCH_M} m per 3D BAG and use its roof, the rest assume ${REPEATER_ANTENNA_HEIGHT_M} m.`
+ : ` Antenna heights: 3D BAG did not answer, all observers assume ${REPEATER_ANTENNA_HEIGHT_M} m.`;
+ clusterStatus.textContent = `Cluster near ${centroid.lat.toFixed(3)}, ${centroid.lon.toFixed(3)}: ${uniquePrefixCount(dedupedNodes)} observers active.${hullNote}${dedupNote}${linkCapNote}${implausibleNote}${roofNote}`;
syncClusterEditorFromLockedRegion();
setDiscoveryCollapsed(true);
renderCandidateMap([]);
@@ -4677,6 +4734,9 @@ Unknown Node Estimate
lat: n.lat, lon: n.lon,
weight: Number.isFinite(n.weight) ? n.weight : 1,
provenRadiusKm: Number.isFinite(n.provenRadiusKm) ? n.provenRadiusKm : null,
+ antennaM: observerAntennaHeightM(n),
+ onRoof: Number.isFinite(n.roofHeightM) ? 1 : 0,
+ roofDistanceM: Number.isFinite(n.roofDistanceM) ? n.roofDistanceM : null,
moved: n.moved ? 1 : 0,
movedKm: n.moved && positionOverrides.get(nodeId(n))
? haversineKm(positionOverrides.get(nodeId(n)).upstream, n) : null
@@ -5285,10 +5345,17 @@ Unknown Node Estimate
? "" : ` Proven-link radius: ${Number(p.provenRadiusKm).toFixed(1)} km`;
const moved = p.moved === 1 || p.moved === "1";
const movedLine = moved ? ` Moved ${Number(p.movedKm).toFixed(1)} km from its advertised position; range from link count.` : "";
+ const onRoof = p.onRoof === 1 || p.onRoof === "1";
+ const roofWhere = onRoof
+ ? (Number(p.roofDistanceM) > 0 ? `tallest roof within ${Number(p.roofDistanceM).toFixed(0)} m, per 3D BAG, + mast` : "its roof per 3D BAG + mast")
+ : "assumed";
+ const antennaLine = Number.isFinite(Number(p.antennaM))
+ ? ` Antenna ${Number(p.antennaM).toFixed(0)} m (${roofWhere})`
+ : "";
const popup = show3dPopup(e.lngLat, `
-
+
${moved ? `Put it back ` : ""}
`);
diff --git a/web-standalone/server.py b/web-standalone/server.py
index af9e797..73e5024 100644
--- a/web-standalone/server.py
+++ b/web-standalone/server.py
@@ -9,6 +9,7 @@
import threading
import time
+import bag3d
import spamdetector
@@ -46,6 +47,32 @@
SPAMDETECTOR_CACHE_SIZE = 20
INCIDENT_ID = re.compile(r"^[0-9]{1,9}$")
+# 3D BAG (#74): the tallest building within BAG3D_RADIUS_M of a repeater,
+# for its antenna height. The API pages at 100 features; a 100 m box holds
+# up to about 100 buildings in a Dutch town centre, so up to BAG3D_MAX_PAGES
+# are followed.
+# Keyed on the rounded position; buildings do not move, so the cache lives a
+# day.
+BAG3D_ITEMS_URL = "https://api.3dbag.nl/collections/pand/items?bbox={bbox}&limit=100"
+BAG3D_RADIUS_M = 50
+BAG3D_MAX_PAGES = 4
+BAG3D_TTL = 86400
+
+
+def _bag3d_pages(url):
+ """Every page of a 3D BAG items query, following rel=next, capped."""
+ pages = []
+ next_url = url
+ while next_url and len(pages) < BAG3D_MAX_PAGES:
+ page = json.loads(_fetch(next_url).decode("utf-8", "replace"))
+ pages.append(page)
+ next_url = None
+ for link in page.get("links") or []:
+ if link.get("rel") == "next" and link.get("href"):
+ next_url = link["href"]
+ break
+ return pages
+
_cache = {}
_cache_lock = threading.Lock()
@@ -107,8 +134,35 @@ def do_GET(self):
if self.path.startswith("/proxy/spamdetector/incident/"):
self._spamdetector_incident(self.path[len("/proxy/spamdetector/incident/"):])
return
+ if self.path.startswith("/proxy/3dbag/roof?"):
+ self._bag3d_roof(self.path.split("?", 1)[1])
+ return
super().do_GET()
+ def _bag3d_roof(self, query):
+ params = dict(part.split("=", 1) for part in query.split("&") if "=" in part)
+ try:
+ lat = float(params.get("lat", ""))
+ lon = float(params.get("lon", ""))
+ except ValueError:
+ self._send_json({"error": "lat and lon must be numbers"}, 400)
+ return
+ if not (50.0 <= lat <= 54.0 and 3.0 <= lon <= 7.5):
+ # Outside the Netherlands there is no 3D BAG; not an error, no roof.
+ self._send_json({"roof_m": None, "building": None, "buildings": 0, "covered": False}, 200)
+ return
+ key = "bag3d:%.5f,%.5f" % (lat, lon)
+ url = BAG3D_ITEMS_URL.format(bbox=bag3d.bbox_around(lat, lon, BAG3D_RADIUS_M))
+ try:
+ result = _cached(key, BAG3D_TTL, lambda: bag3d.roof_for_point(_bag3d_pages(url), lat, lon, BAG3D_RADIUS_M))
+ except UpstreamError as error:
+ self._send_json({"error": error.message}, error.status)
+ return
+ except ValueError:
+ self._send_json({"error": "3D BAG answered something that is not JSON"}, 502)
+ return
+ self._send_json(dict(result, covered=True, radius_m=BAG3D_RADIUS_M), 200)
+
def _spamdetector_alerts(self):
try:
body = _cached("alerts", SPAMDETECTOR_ALERTS_TTL, lambda: _fetch(SPAMDETECTOR_ALERTS_URL))
diff --git a/web-standalone/tests/fixtures/bag3d-pand.json b/web-standalone/tests/fixtures/bag3d-pand.json
new file mode 100644
index 0000000..0edb591
--- /dev/null
+++ b/web-standalone/tests/fixtures/bag3d-pand.json
@@ -0,0 +1 @@
+{"type":"FeatureCollection","numberMatched":1,"numberReturned":1,"metadata":{"transform":{"scale":[0.001,0.001,0.001],"translate":[121651.2191875,487466.72225,1.2664794901695586e-06]}},"features":[{"id":"NL.IMBAG.Pand.0363100012178469","type":"CityJSONFeature","vertices":[[81299,39690,0],[76346,45912,0],[69161,48332,0],[68531,61810,0],[58691,64030,0],[52281,64710,0],[50761,64621,0],[50671,65800,0],[49662,66031,0],[49595,66027,0],[49694,64591,0],[39636,63894,0],[39531,65422,0],[38551,65351,0],[38657,63826,0],[37165,63723,0],[37184,63305,0],[34038,63164,0],[34055,62616,0],[31531,63871,0],[31481,64301,0],[31163,64263,0],[31220,63780,0],[28981,62202,0],[28545,62416,0],[28404,62131,0],[28791,61938,0],[28878,60021,0],[27990,59981,0],[27300,59181,0],[24186,61861,0],[24165,61946,0],[8806,58141,0],[8842,57507,0],[8761,57521,0],[7852,57452,0],[7917,56511,0],[6993,56441,0],[7030,55561,0],[5225,55473,0],[5232,55386,0],[3133,53169,0],[3037,53172,0],[2681,49501,0],[6912,49280,0],[6820,50109,0],[8358,50318,0],[8931,38833,0],[1471,38461,0],[102,38442,0],[149,35121,0],[0,35118,0],[13,34269,0],[163,34270,0],[202,31621,0],[52,31618,0],[66,30668,0],[216,30671,0],[261,27570,0],[1631,27590,0],[9471,27982,0],[9898,19444,0],[5895,19246,0],[4959,18047,0],[5070,15811,0],[6121,14711,0],[10123,14909,0],[10502,7313,0],[26546,8463,0],[27060,1280,0],[32932,1702,0],[32809,3386,0],[35312,2499,0],[35267,2013,0],[35711,1971,0],[35751,2390,0],[40790,2680,0],[41013,2482,0],[42040,2540,0],[42121,1350,0],[42640,1096,0],[42946,1109,0],[42955,863,0],[43055,869,0],[43397,100,0],[43401,0,0],[52765,385,0],[52761,485,0],[53038,1277,0],[53137,1281,0],[53128,1526,0],[53916,1558,0],[53862,3108,0],[55392,3162,0],[55357,4151,0],[53827,4096,0],[53372,16996,0],[67354,17490,0],[67601,10465,0],[67882,10480,0],[72191,15811,0],[70601,17604,0],[70327,23436,0],[77363,26564,0],[81700,33179,0],[31531,63871,1133],[34055,62616,1133],[34046,62907,1133],[34350,63178,1133],[37184,63305,1133],[37165,63723,1133],[38657,63826,1133],[38551,65351,1133],[39531,65422,1133],[39636,63894,1133],[49694,64591,1133],[49595,66027,1133],[49662,66031,1133],[50671,65800,1133],[50761,64621,1133],[52281,64710,1133],[58691,64030,1133],[68531,61810,1133],[69161,48332,1133],[76346,45912,1133],[81299,39690,1133],[81700,33179,1133],[77363,26564,1133],[70327,23436,1133],[70601,17604,1133],[72191,15811,1133],[67882,10480,1133],[67601,10465,1133],[67354,17490,1133],[53372,16996,1133],[53827,4096,1133],[55357,4151,1133],[55392,3162,1133],[53862,3108,1133],[53916,1558,1133],[53128,1526,1133],[53137,1281,1133],[53038,1277,1133],[52761,485,1133],[52765,385,1133],[43624,10,1133],[43760,1307,1133],[42335,1246,1133],[42121,1350,1133],[42040,2540,1133],[41013,2482,1133],[40790,2680,1133],[35751,2390,1133],[35711,1971,1133],[35267,2013,1133],[35312,2499,1133],[32809,3386,1133],[32932,1702,1133],[27060,1280,1133],[26546,8463,1133],[10502,7313,1133],[10123,14909,1133],[6121,14711,1133],[5070,15811,1133],[4959,18047,1133],[5895,19246,1133],[9898,19444,1133],[9471,27982,1133],[1631,27590,1133],[261,27570,1133],[216,30671,1133],[66,30668,1133],[52,31618,1133],[202,31621,1133],[163,34270,1133],[13,34269,1133],[0,35118,1133],[149,35121,1133],[102,38442,1133],[1471,38461,1133],[8931,38833,1133],[8358,50318,1133],[6820,50109,1133],[6912,49280,1133],[2681,49501,1133],[3037,53172,1133],[3133,53169,1133],[5232,55386,1133],[5225,55473,1133],[7030,55561,1133],[6993,56441,1133],[7917,56511,1133],[7852,57452,1133],[8761,57521,1133],[8842,57507,1133],[8806,58141,1133],[24165,61946,1133],[24186,61861,1133],[27300,59181,1133],[27990,59981,1133],[28878,60021,1133],[28791,61938,1133],[28404,62131,1133],[28545,62416,1133],[28981,62202,1133],[31220,63780,1133],[31163,64263,1133],[31481,64301,1133],[76346,45912,21774],[69161,48332,21774],[81299,39690,21774],[68531,61810,21774],[58691,64030,21774],[52281,64710,21774],[50761,64621,21774],[50671,65800,21774],[49662,66031,21774],[49595,66027,21774],[49694,64591,21774],[39636,63894,21774],[38657,63826,21774],[37165,63723,21774],[37184,63305,21774],[39531,65422,21774],[38551,65351,21774],[31220,63780,21774],[28981,62202,21774],[34055,62616,21774],[31531,63871,21774],[31481,64301,21774],[31163,64263,21774],[28545,62416,21774],[28404,62131,21774],[28791,61938,21774],[28878,60021,21774],[27990,59981,21774],[27300,59181,21774],[2681,49501,21774],[6912,49280,21774],[6820,50109,21774],[24186,61861,21774],[24165,61946,21774],[8806,58141,21774],[8842,57507,21774],[8761,57521,21774],[7852,57452,21774],[7917,56511,21774],[6993,56441,21774],[7030,55561,21774],[8358,50318,21774],[5225,55473,21774],[5232,55386,21774],[3133,53169,21774],[3037,53172,21774],[8931,38833,21774],[1471,38461,21774],[13,34269,21774],[163,34270,21774],[102,38442,21774],[149,35121,21774],[0,35118,21774],[66,30668,21774],[216,30671,21774],[202,31621,21774],[1631,27590,21774],[9471,27982,21774],[52,31618,21774],[261,27570,21774],[9898,19444,21774],[5895,19246,21774],[4959,18047,21774],[5070,15811,21774],[6121,14711,21774],[10123,14909,21774],[10502,7313,21774],[26546,8463,21774],[27060,1280,21774],[32932,1702,21774],[32809,3386,21774],[35312,2499,21774],[35751,2390,21774],[40790,2680,21774],[35267,2013,21774],[35711,1971,21774],[41013,2482,21774],[42040,2540,21774],[42121,1350,21774],[42335,1246,21774],[53862,3108,21774],[55392,3162,21774],[53916,1558,21774],[52765,385,21774],[52761,485,21774],[53038,1277,21774],[53137,1281,21774],[53128,1526,21774],[55357,4151,21774],[53827,4096,21774],[53372,16996,21774],[67354,17490,21774],[67601,10465,21774],[67882,10480,21774],[72191,15811,21774],[70601,17604,21774],[70327,23436,21774],[77363,26564,21774],[81700,33179,21774],[34350,63178,21774],[34046,62907,21774],[43624,10,21774],[43760,1307,21774],[1455,27588,1133],[322,27571,1133],[86,31619,1133],[180,33076,1133],[129,36509,1133],[204,38443,1133],[3961,38586,1133],[5595,38667,1133],[5947,38685,1133],[6359,38705,1133],[8627,44938,1133],[8417,49143,1133],[7002,56228,1133],[7114,56450,1133],[28707,61979,1133],[28741,62320,1133],[33368,62957,1133],[33638,62823,1133],[38616,63824,1133],[43712,64177,1133],[44987,64265,1133],[53433,64588,1133],[68550,61399,1133],[70417,47909,1133],[70740,23619,1133],[70584,17969,1133],[67405,16037,1133],[53910,1744,1133],[40379,2657,1133],[26585,7927,1133],[6106,14725,1133],[9885,19444,1133],[9894,19512,1133],[9563,26149,1133],[8233,27920,1133],[2944,27656,1133],[38618,63694,21110],[38618,63694,13449],[38967,63668,13449],[38967,63668,21110],[38616,63824,21110],[38616,63824,13449],[76346,45912,16552],[70417,47909,16552],[70437,47636,21110],[70437,47636,16552],[70417,47909,21110],[81299,39690,16552],[68550,61399,21110],[68550,61399,4535],[60982,61156,4535],[60982,61156,21110],[68531,61810,4535],[58691,64030,4535],[52281,64710,21110],[50761,64621,21110],[39169,58365,13449],[39169,58365,7211],[39490,58733,7211],[39490,58733,13449],[39490,58733,21110],[39460,57655,7211],[39460,57655,21110],[50671,65800,21110],[49662,66031,21110],[49595,66027,21110],[39531,65422,21110],[38551,65351,21110],[39636,63894,21110],[28791,61938,7211],[28878,60021,7211],[31481,64301,7211],[31163,64263,7211],[28741,62320,7211],[28741,62320,1516],[28707,61979,1516],[28707,61979,7211],[31220,63780,7211],[28404,62131,1516],[27990,59981,7211],[27300,59181,7211],[2681,49501,10301],[6912,49280,10301],[6820,50109,10301],[24186,61861,7211],[24165,61946,7211],[8806,58141,7211],[8842,57507,7211],[8761,57521,7211],[7852,57452,7211],[7917,56511,7211],[5232,55386,10301],[3133,53169,10301],[8358,50318,10301],[5225,55473,10301],[3037,53172,10301],[216,30671,9268],[261,27570,9268],[66,30668,9268],[0,35118,9319],[13,34269,9319],[1631,27590,9275],[2944,27656,9275],[202,31621,37034],[86,31619,9268],[86,31619,37034],[10285,36104,37230],[10285,36104,25796],[10821,35584,25796],[10821,35584,37230],[26585,7927,21090],[26585,7927,9070],[27203,7343,9070],[27203,7343,21090],[4959,18047,15729],[5070,15811,15729],[27060,1280,9070],[32932,1702,9070],[32809,3386,9070],[35711,1971,9070],[35751,2390,9070],[35312,2499,9070],[35267,2013,9070],[32711,8346,21090],[32711,8346,9070],[33023,8055,9070],[33023,8055,21090],[40790,2680,21090],[41013,2482,21090],[53433,64588,21110],[53433,64588,4535],[51941,60453,21110],[51941,60453,4535],[42040,2540,21090],[42121,1350,21090],[42335,1246,21090],[53916,1558,9306],[53910,1744,9306],[52765,385,9306],[52761,485,9306],[48568,3972,21090],[48568,3972,9306],[52008,1662,9306],[52008,1662,21090],[53038,1277,9306],[53137,1281,9306],[53128,1526,9306],[55357,4151,21090],[53827,4096,21090],[53862,3108,21090],[55392,3162,21090],[53372,16996,21090],[67354,17490,21090],[67405,16037,4603],[67405,16037,21090],[72191,15811,4603],[70601,17604,4603],[67601,10465,4603],[67882,10480,4603],[70051,17925,4603],[70051,17925,21090],[70584,17969,4603],[70584,17969,21090],[70644,35967,25796],[70644,35967,21090],[69635,38362,21090],[69635,38362,25796],[81700,33179,16552],[77363,26564,16552],[10123,14909,21090],[10502,7313,21090],[6106,14725,21090],[6106,14725,15729],[8981,16669,21090],[8981,16669,15729],[6703,16487,15729],[6703,16487,21090],[1832,37330,37006],[1832,37330,9456],[846,36981,9456],[846,36981,37006],[920,36871,9456],[920,36871,37006],[6359,38705,35559],[6359,38705,7648],[5947,38685,35559],[8620,52538,21110],[8620,52538,10301],[8123,51694,10301],[8123,51694,21110],[8651,49115,10301],[8651,49115,21110],[102,38442,9456],[129,36509,9319],[129,36509,9456],[7929,36722,45078],[7929,36722,37230],[7870,36889,37230],[7870,36889,45078],[72765,28211,21090],[72765,28211,16552],[72971,28202,16552],[72971,28202,21090],[62284,29709,25796],[62284,29709,21090],[62132,29415,21090],[62132,29415,25796],[56727,30256,25796],[56727,30256,21090],[26112,37652,25796],[26112,37652,15114],[25364,39280,15114],[25364,39280,21110],[25364,39280,25796],[13437,32513,36631],[13437,32513,25796],[11596,32411,25796],[11596,32411,36631],[33759,33933,25796],[33759,33933,15798],[30527,39580,15798],[30527,39580,21110],[30527,39580,25796],[26546,8463,21090],[70327,23436,21090],[70740,23619,16552],[70740,23619,21090],[27199,39305,21110],[27199,39305,25796],[8627,44938,21110],[8627,44938,7648],[9263,44959,7648],[9263,44959,21110],[41889,39908,25796],[41889,39908,21110],[42421,38088,21110],[42421,38088,25796],[1471,38461,37006],[204,38443,9456],[204,38443,37006],[1913,37500,9456],[1913,37500,37006],[67027,41720,25796],[67027,41720,21110],[67027,41720,15256],[66275,40433,15256],[66275,40433,25796],[69130,41664,25796],[69130,41664,21110],[72502,30552,25796],[72502,30552,21090],[70575,33982,21090],[70575,33982,25796],[60653,41323,25796],[60653,41323,21110],[60117,40549,21110],[60117,40549,25796],[70207,41756,25796],[70207,41756,21110],[69179,41710,21110],[69179,41710,25796],[62852,41423,25796],[62852,41423,21110],[50887,60371,21110],[50887,60371,14456],[51737,51845,14456],[51737,51845,21110],[69161,48332,21110],[7114,56450,21110],[7114,56450,7211],[6993,56441,21110],[7002,56228,10301],[7002,56228,21110],[53910,1744,21090],[52970,60533,21110],[52970,60533,4535],[5947,38685,45078],[5595,38667,9250],[5595,38667,37006],[5595,38667,45078],[12366,25873,21090],[12366,25873,15055],[12691,26280,15055],[12691,26280,21090],[9563,26149,36631],[9563,26149,21090],[12691,26280,36631],[17158,26468,25796],[17158,26468,21090],[25836,26834,21090],[25836,26834,25796],[16222,27988,25796],[16222,27988,15055],[17158,26468,15055],[17397,26079,15055],[17397,26079,21090],[9885,19444,21090],[9885,19444,15729],[9662,17709,15729],[9662,17709,21090],[35486,38342,25796],[35486,38342,15798],[34076,32121,25796],[34076,32121,21090],[39501,29223,21090],[39501,29223,25796],[32343,39730,15798],[32343,39730,21110],[40199,38533,25796],[40199,38533,21110],[40199,38533,15798],[40168,38448,15798],[40168,38448,25796],[69677,29948,25796],[69677,29948,21090],[70025,29559,21090],[70025,29559,25796],[70442,28314,16552],[70442,28314,21090],[5895,19246,15729],[2387,28429,37034],[2387,28429,35549],[2387,28429,9275],[2944,27656,37034],[2018,32697,45078],[2018,32697,37034],[3337,30039,37034],[3337,30039,45078],[7030,55561,10301],[7281,55591,10301],[7281,55591,21110],[13788,39021,25796],[13788,39021,21110],[13586,37029,21110],[13586,37029,25796],[41680,16589,21090],[41680,16589,14747],[42549,16705,14747],[42549,16705,21090],[27150,8750,9070],[27150,8750,21090],[31995,9012,9070],[31995,9012,21090],[51223,40208,25796],[51223,40208,21110],[50651,40871,21110],[50651,40871,25796],[58204,40492,21110],[58204,40492,25511],[58204,40492,25796],[57689,41189,21110],[57689,41189,25511],[51969,40930,25796],[51969,40930,21110],[52109,40091,21110],[52109,40091,25796],[37184,63305,13449],[37184,63305,7211],[37931,61444,7211],[37931,61444,13449],[31531,63871,7211],[33368,62957,7211],[33368,62957,1487],[34350,63178,7211],[34046,62907,7211],[34055,62616,7211],[25806,39314,21110],[25806,39314,15114],[26468,39244,15114],[26468,39244,21110],[27199,39305,15114],[10212,36710,25796],[10212,36710,37230],[1563,35916,45078],[1563,35916,37006],[2220,34940,37006],[2220,34940,45078],[63414,42074,21110],[63414,42074,15256],[67312,42209,15256],[67312,42209,21110],[58005,40486,25796],[58005,40486,25511],[56508,38482,25511],[56508,38482,25796],[32409,39824,15798],[32409,39824,21110],[43760,1307,9306],[43624,10,9306],[43760,1307,21090],[48185,1498,9306],[48185,1498,21090],[48079,3908,9306],[48079,3908,21090],[40545,39471,21110],[40545,39471,15798],[36608,40585,21110],[36608,40585,15798],[39306,40819,15798],[39306,40819,21110],[41202,40244,21110],[41202,40244,15798],[51349,40096,25796],[51349,40096,21110],[50651,60353,21110],[50651,60353,14456],[42413,6548,21090],[42413,6548,9070],[42225,6276,9070],[42225,6276,21090],[47293,34916,34962],[47293,34916,25796],[45496,34744,25796],[45496,34744,34962],[19529,39972,25796],[19529,39972,21110],[17201,40216,21110],[17201,40216,25796],[30026,58377,21110],[30026,58377,7211],[29519,57394,7211],[29519,57394,21110],[9288,38680,7648],[9288,38680,21110],[28981,62202,7211],[28545,62416,1516],[25325,39364,25796],[25325,39364,21110],[21887,39725,21110],[21887,39725,25796],[3961,38586,9250],[3868,38049,9250],[3868,38049,37006],[47540,40090,25796],[47540,40090,21110],[43351,40483,21110],[43351,40483,25796],[50572,59062,21110],[50572,59062,14456],[50784,51855,21110],[50784,51855,14456],[49694,64591,21110],[44987,64265,5150],[44987,64265,21110],[29552,57084,7211],[29552,57084,21110],[28491,56946,7211],[28491,56946,21110],[29684,55816,21110],[29684,55816,14047],[30229,50620,14047],[30229,50620,21110],[29438,50604,14047],[29438,50604,21110],[29184,55890,21110],[29184,55890,14047],[32184,8333,21090],[32184,8333,9070],[32154,8864,21090],[32154,8864,9070],[3411,35403,57215],[3411,35403,45078],[3134,33794,45078],[3134,33794,57215],[4120,35244,57215],[4120,35244,45078],[3961,38586,37006],[4758,36259,57215],[4758,36259,45078],[10660,33536,45078],[10660,33536,25796],[9731,35007,25796],[9731,35007,37230],[9731,35007,45078],[9301,35688,37230],[9301,35688,45078],[8417,49143,21110],[8417,49143,10301],[7037,37891,45078],[7037,37891,37230],[7037,37891,35559],[6601,37624,35559],[6601,37624,45078],[49064,28491,25796],[49064,28491,21090],[51495,28663,21090],[51495,28663,25796],[49039,28835,25796],[49039,28835,21090],[37165,63723,13449],[34870,40014,15798],[34870,40014,21110],[40156,57764,21110],[40156,57764,14279],[40759,51179,14279],[40759,51179,21110],[39583,57674,21110],[39583,57674,14279],[39984,51165,21110],[39984,51165,14279],[55605,41095,25796],[55605,41095,25511],[55605,41095,21110],[48117,34020,34962],[48117,34020,25796],[5833,33397,62022],[5833,33397,57215],[5680,33580,57215],[5680,33580,62022],[5674,33949,57215],[5674,33949,62022],[8738,37606,37230],[8738,37606,7648],[7899,38419,7648],[7899,38419,37230],[10472,37002,25796],[10472,37002,37230],[10472,37002,21110],[9579,38269,21110],[9579,38269,37230],[53288,60558,4535],[53288,60558,21110],[9579,38269,7648],[53401,59694,4535],[53401,59694,21110],[42226,9564,21090],[42226,9564,14747],[42644,9586,21090],[42644,9586,14747],[41490,27493,25796],[41490,27493,21090],[42868,27553,21090],[42868,27553,25796],[42226,9564,9070],[42421,7047,9070],[42421,7047,21090],[38305,9352,21090],[38305,9352,9070],[37471,9564,21090],[37471,9564,9070],[38294,9452,9070],[38294,9452,21090],[41615,5768,9070],[41615,5768,21090],[35175,9183,21090],[35175,9183,9070],[37481,9308,9070],[37481,9308,21090],[40379,2657,9070],[40315,4686,21090],[40315,4686,9070],[40379,2657,21090],[62852,41423,15256],[64504,41498,25796],[64504,41498,15256],[33648,57188,21110],[33648,57188,7211],[33136,57548,7211],[33136,57548,21110],[34475,57451,21110],[34475,57451,7211],[10780,37347,25796],[10780,37347,21110],[15484,41754,21110],[15484,41754,25796],[15642,28930,36631],[15642,28930,25796],[21054,37936,25796],[21054,37936,21110],[47664,33353,34962],[47664,33353,25796],[47604,33409,34962],[47604,33409,25796],[46342,33269,34962],[46342,33269,25796],[51302,29945,25796],[51302,29945,21090],[52601,29999,21090],[52601,29999,25796],[5540,33380,57215],[5540,33380,62022],[35000,39824,21110],[35000,39824,15798],[39665,40511,15798],[39665,40511,21110],[41025,40775,15798],[41025,40775,21110],[14636,41716,21110],[14636,41716,25796],[45011,63893,21110],[45011,63893,5150],[43712,64177,5150],[43712,64177,21110],[73739,30485,16552],[73739,30485,21090],[75464,34064,21090],[75464,34064,16552],[75740,34458,16552],[75740,34458,21090],[75419,34525,16552],[75419,34525,21090],[75365,34660,21090],[75365,34660,16552],[75287,37649,16552],[75287,37649,21090],[40778,29241,21090],[40778,29241,25796],[41176,28269,21090],[41176,28269,25796],[43153,28042,25796],[43153,28042,21090],[43343,27651,21090],[43343,27651,25796],[40971,40628,15798],[40971,40628,21110],[9386,17625,15729],[9386,17625,21090],[9894,19512,21090],[74513,32785,21090],[74513,32785,16552],[75403,33998,16552],[75403,33998,21090],[8931,38833,7648],[8000,36733,45078],[8000,36733,37230],[9078,35437,45078],[9078,35437,37230],[8109,34351,53806],[8109,34351,45078],[8412,34690,45078],[8412,34690,53806],[31235,57302,21110],[31235,57302,7211],[33388,62757,7211],[33388,62757,1487],[33638,62823,1487],[5467,36363,57215],[5467,36363,45078],[12975,40759,25796],[12975,40759,21110],[48559,40123,25796],[48559,40123,21110],[48589,40777,25796],[48589,40777,21110],[52626,29142,25796],[52626,29142,21090],[53057,29259,21090],[53057,29259,25796],[73917,31013,21090],[73917,31013,16552],[39266,62816,21110],[39266,62816,13449],[39498,59048,13449],[39498,59048,21110],[8438,55926,10301],[8438,55926,21110],[8761,53566,10301],[8761,53566,21110],[5618,37270,45078],[5618,37270,37006],[28458,56693,7211],[28458,56693,21110],[34562,56890,7211],[34562,56890,21110],[14209,28179,36631],[14209,28179,25796],[14041,28653,25796],[14041,28653,36631],[70335,41762,25796],[70335,41762,16552],[70207,41756,16552],[5936,35055,57215],[5936,35055,53806],[5936,35055,45078],[71210,28280,16552],[71210,28280,21090],[73713,31038,21090],[73713,31038,16552],[73370,30900,16552],[73370,30900,21090],[65348,40705,25796],[65348,40705,15256],[65361,40949,15256],[65361,40949,25796],[65723,40717,25796],[65723,40717,15256],[66126,40459,25796],[66126,40459,15256],[129,36509,37006],[1211,34322,37006],[1211,34322,45078],[7037,37891,7648],[14209,28179,15055],[14849,26371,15055],[14849,26371,25796],[5730,28862,45078],[5730,28862,37034],[6394,29333,37034],[6394,29333,45078],[9471,27982,36631],[8233,27920,36631],[6635,29505,57215],[6635,29505,45078],[6635,29505,36631],[7127,29854,36631],[7127,29854,45078],[7127,29854,57215],[9033,33625,57215],[9033,33625,45078],[8982,33766,45078],[8982,33766,57215],[11272,32800,36631],[11272,32800,45078],[11272,32800,25796],[1211,34322,9319],[149,35121,9319],[43067,39946,25796],[43067,39946,21110],[42049,4093,9070],[42049,4093,21090],[33661,62678,7211],[33661,62678,1487],[34176,8267,9070],[34176,8267,21090],[34139,8380,9070],[34139,8380,21090],[70099,41117,21090],[70099,41117,25796],[6036,35477,53806],[6036,35477,45078],[6021,34819,57215],[6021,34819,53806],[7308,36479,53806],[7308,36479,45078],[71277,41591,21090],[71277,41591,16552],[70283,41621,16552],[70283,41621,21090],[70283,41621,25796],[74938,38612,16552],[74938,38612,21090],[5109,30884,57215],[5109,30884,45078],[5396,31023,45078],[5396,31023,57215],[8233,27920,37034],[40400,4757,21090],[40400,4757,9070],[70353,42622,16552],[70353,42622,21110],[70785,42958,16552],[70785,42958,21110],[70510,46649,16552],[70510,46649,21110],[69000,47560,16552],[69000,47560,21110],[71749,29016,21090],[71749,29016,16552],[45692,34602,34962],[45692,34602,25796],[45086,34547,25796],[45086,34547,34962],[1069,30257,9268],[1069,30257,37034],[53666,28816,21090],[53666,28816,25796],[56625,29025,21090],[56625,29025,25796],[56546,30232,21090],[56546,30232,25796],[73823,39459,16552],[73823,39459,21090],[8258,53362,10301],[8258,53362,21110],[33638,62823,7211],[163,34270,9319],[180,33076,9319],[8109,34351,57215],[73451,40517,21090],[73451,40517,16552],[27552,30992,25796],[27552,30992,21090],[1432,33147,37034],[1432,33147,9319],[180,33076,37034],[483,33876,45078],[483,33876,9319],[1432,33147,45078],[6394,29333,36631],[8703,34218,45078],[8703,34218,57215],[322,27571,9268],[859,29915,9268],[859,29915,37034],[1522,29628,37034],[1522,29628,9268],[1750,29312,9268],[1750,29312,37034],[14907,26374,25796],[14907,26374,15055],[15846,27767,15055],[15846,27767,25796],[1664,29208,9268],[1664,29208,35549],[1664,29208,37034],[322,27571,35549],[1455,27588,9275],[1455,27588,35549],[1783,29266,37034],[1783,29266,35549],[5340,31092,57215],[5340,31092,45078],[34883,8397,9070],[34883,8397,21090],[35444,8501,21090],[35444,8501,9070],[38650,61237,7211],[38650,61237,13449],[38671,59603,13449],[38671,59603,7211],[68601,60295,1133],[68808,55883,1133],[68926,53371,1133],[72932,47061,1133],[76448,45782,1133],[78527,43171,1133],[80612,40552,1133],[81053,39998,1133],[81494,36518,1133],[80931,32007,1133],[79347,29592,1133],[77493,26763,1133],[74321,25212,1133],[53387,16563,1133],[38982,2576,1133],[33172,3257,1133],[32913,1961,1133],[27636,1322,1133],[21431,8096,1133],[16178,7720,1133],[10161,14159,1133],[5007,17099,1133],[6119,19257,1133],[9839,20616,1133],[8921,39045,1133],[3476,49459,1133],[4200,54296,1133],[25702,60555,1133],[28853,60564,1133],[29139,62313,1133],[31129,63716,1133],[33968,62659,1133],[36033,63254,1133],[38021,58311,7279],[38021,58311,6431],[38136,57598,7155],[38159,57452,7303],[38159,57452,7129],[80612,40552,13998],[80612,40552,13931],[80383,40496,14005],[75925,39405,15452],[75925,39405,14138],[78527,43171,19136],[76448,45782,12993],[76448,45782,13787],[68531,61810,4546],[58691,64030,4513],[61219,61163,17244],[61219,61163,4556],[60982,61156,4557],[60982,61156,17221],[52281,64710,10496],[50761,64621,13238],[39169,58365,7851],[39169,58365,5353],[39490,58733,4781],[39490,58733,8692],[39490,58733,13181],[39460,57655,4934],[39460,57655,12952],[45972,46697,23361],[45972,46697,23662],[46053,46703,23514],[49694,64591,15157],[44987,64265,4965],[44987,64265,23653],[45011,63893,5425],[45011,63893,23647],[50671,65800,13278],[49662,66031,15071],[49595,66027,15190],[39531,65422,14342],[38551,65351,12497],[39636,63894,14293],[38618,63694,13910],[38618,63694,12355],[38657,63826,12450],[38657,63826,14123],[28791,61938,6128],[28853,60564,6115],[31481,64301,5624],[31163,64263,5782],[34350,63178,7428],[34046,62907,6885],[31531,63871,6165],[28741,62320,6084],[28741,62320,1423],[28707,61979,1759],[28707,61979,6012],[28878,60021,8108],[27990,59981,8295],[24165,61946,4857],[8806,58141,5143],[8842,57507,5153],[8761,57521,5154],[7852,57452,5168],[7917,56511,5182],[6820,50109,9044],[8358,50318,9501],[3037,53172,6856],[2681,49501,7380],[4200,54296,8335],[3133,53169,7006],[0,35118,8634],[13,34269,8634],[86,31619,8675],[66,30668,8631],[27552,30992,24270],[27552,30992,22675],[29053,31252,23166],[29053,31252,24519],[29053,31252,24573],[5070,15811,13268],[6106,14725,15099],[28495,33276,27933],[28495,33276,27648],[33409,33662,27774],[35312,2499,5054],[35267,2013,4234],[35711,1971,4271],[38305,9352,22225],[38305,9352,6672],[42226,9564,6752],[42226,9564,13810],[42226,9564,14519],[40790,2680,10854],[41013,2482,11232],[53433,64588,8437],[53433,64588,4505],[51941,60453,11538],[51941,60453,4568],[42040,2540,13036],[42121,1350,13100],[42335,1246,13469],[53916,1558,9473],[53910,1744,9823],[52765,385,7371],[52761,485,7560],[53038,1277,9027],[53137,1281,9025],[53128,1526,9487],[68720,47545,23677],[68720,47545,23031],[69000,47560,23683],[55357,4151,11543],[53827,4096,14100],[41490,27493,15302],[41490,27493,15763],[28331,26939,14804],[25836,26834,14622],[25836,26834,14710],[53862,3108,14128],[55392,3162,11571],[65478,29651,27193],[65478,29651,14443],[69677,29948,14418],[69677,29948,18748],[72191,15811,4506],[70601,17604,4630],[53387,16563,13748],[53372,16996,14367],[67354,17490,13537],[81700,33179,14376],[81700,33179,14323],[76036,34397,14405],[70051,17925,13862],[70051,17925,4662],[70584,17969,4644],[70584,17969,13866],[76346,45912,13250],[75108,43587,13115],[75108,43587,13927],[80931,32007,14452],[81494,36518,19197],[68741,47392,23937],[68741,47392,22796],[10123,14909,15205],[1832,37330,35872],[1832,37330,11049],[846,36981,9765],[846,36981,35290],[920,36871,9859],[920,36871,35454],[163,34270,8826],[180,33076,8826],[25998,45107,23423],[25998,45107,23354],[27340,45214,23390],[28278,45289,23415],[28278,45289,23367],[70740,23619,21910],[70740,23619,12942],[70663,24822,13171],[70663,24822,22409],[70663,24822,23635],[74321,25212,18956],[57810,48473,20924],[57810,48473,20755],[57702,48464,20926],[33219,21501,23676],[33219,21501,23643],[35033,21613,23609],[35033,21613,23690],[46436,35644,25819],[46436,35644,27397],[45329,35650,25707],[28465,33203,28047],[28465,33203,27541],[8412,34690,45142],[8412,34690,43873],[9078,35437,43882],[9078,35437,44968],[77363,26564,13639],[77493,26763,13260],[29552,57084,13961],[29552,57084,13008],[29684,55816,12874],[29684,55816,13988],[29184,55890,13947],[40168,38448,20750],[40168,38448,15926],[35486,38342,15710],[35486,38342,16518],[35486,38342,20484],[8627,44938,7209],[8921,39045,7347],[47278,22036,22715],[47278,22036,22228],[47258,22494,22885],[47258,22494,23458],[33759,33933,27374],[33759,33933,20217],[30527,39580,15410],[30527,39580,18041],[38481,33869,27916],[38481,33869,27678],[38511,33950,27788],[16217,45191,22109],[16217,45191,22131],[16937,46149,20508],[19977,50192,13659],[19977,50192,13751],[15587,45170,23357],[15587,45170,22094],[16206,45190,22130],[13437,32513,27749],[13437,32513,27000],[13671,32132,26470],[13671,32132,26663],[13671,32132,28380],[14064,32690,27462],[14064,32690,27525],[28162,33191,28038],[28162,33191,27551],[28162,33191,27467],[28706,32007,25701],[16409,20334,23746],[16409,20334,23339],[24335,20889,23506],[24335,20889,23706],[70327,23436,21694],[8627,44938,22041],[9263,44959,7536],[9263,44959,22057],[48419,34464,27289],[48419,34464,27054],[56124,34969,26742],[56124,34969,27034],[15667,44478,23356],[15667,44478,23336],[15627,44800,22758],[15627,44800,22824],[15627,44800,23347],[15309,44789,22817],[26112,37652,20713],[26112,37652,15135],[25364,39280,15104],[25364,39280,18040],[39665,40511,15628],[39665,40511,15067],[39306,40819,15570],[25806,39314,15120],[25806,39314,14553],[25364,39280,14543],[30527,39580,14512],[27199,39305,14407],[27199,39305,15168],[27199,39305,18171],[37294,45802,23507],[37294,45802,23345],[38671,45939,23424],[9563,26149,14417],[9839,20616,23426],[33148,3721,6565],[33148,3721,6558],[33148,3721,6124],[33161,3465,6142],[33172,3257,6157],[33172,3257,5799],[9839,20616,23754],[9894,19512,22099],[56268,46706,23713],[56268,46706,22929],[65493,34630,26120],[65493,34630,27745],[65165,33769,26955],[26700,20960,23537],[26700,20960,23488],[27483,20170,22130],[26600,20964,23822],[26600,20964,23670],[26700,20960,23839],[73713,31038,17939],[73713,31038,14183],[73917,31013,14394],[73917,31013,17697],[58204,40492,14993],[58204,40492,13286],[57689,41189,14397],[60117,40549,19734],[60117,40549,13193],[58204,40492,23333],[72932,47061,19171],[70417,47909,15213],[8931,38833,8246],[6359,38705,8488],[39583,57674,14223],[39583,57674,13184],[39460,57655,14436],[56048,46955,23271],[56048,46955,23330],[56063,46938,23302],[53288,60558,16919],[53288,60558,4566],[53401,59694,4579],[53401,59694,18096],[61963,58873,19531],[61963,58873,20204],[63161,59589,19424],[68601,60295,19269],[68601,60295,18583],[68550,61399,4552],[68550,61399,19127],[7114,56450,11623],[7114,56450,5195],[6993,56441,11420],[7002,56228,6705],[7002,56228,11391],[31377,45438,23463],[31377,45438,23350],[53910,1744,14166],[52970,60533,18716],[52970,60533,4566],[51941,60453,16446],[5887,34848,56970],[5887,34848,56042],[5658,34900,55712],[5658,34900,56971],[6119,19257,12980],[6119,19257,12930],[5895,19246,12694],[9563,26149,37456],[12691,26280,14421],[12691,26280,14863],[12691,26280,29680],[15652,20841,22887],[15652,20841,23845],[15953,20540,23382],[25836,26834,18253],[17158,26468,14381],[17158,26468,15041],[17397,26079,15005],[16401,19935,22697],[16401,19935,22906],[16514,19945,22703],[16401,19935,24122],[16409,20334,24127],[29206,45333,23396],[35113,45833,23197],[35113,45833,23224],[30229,50620,14095],[35113,45833,23236],[34232,56696,23528],[43153,28042,21963],[43153,28042,17054],[43343,27651,17331],[43343,27651,22183],[44419,27875,18961],[44419,27875,19151],[44419,27875,23695],[9267,44078,23503],[9267,44078,23368],[14993,44436,23371],[15667,44478,23372],[27636,1322,6444],[32932,1702,6469],[32913,1961,6900],[32913,1961,6939],[30113,4589,12030],[27060,1280,6269],[27636,1322,7301],[7030,55561,7573],[5225,55473,7135],[21157,32913,27828],[21157,32913,27423],[27150,8750,23766],[27150,8750,6692],[31995,9012,7114],[31995,9012,14899],[28853,60564,8117],[27489,1658,7069],[60653,41323,18770],[60653,41323,14322],[60117,40549,19775],[56271,34990,27014],[56271,34990,26982],[56229,35042,26908],[56229,35042,26941],[56224,34951,27065],[56224,34951,26961],[10502,7313,14764],[16178,7720,23764],[16178,7720,23987],[32343,39730,15306],[32343,39730,14569],[32409,39824,15227],[32409,39824,14706],[47258,22494,23588],[43467,22229,23608],[47286,21857,23464],[47286,21857,21972],[47278,22036,23462],[46870,22016,22746],[43760,1307,9961],[43624,10,7530],[43760,1307,15970],[48185,1498,9900],[48185,1498,23737],[129,36509,8828],[129,36509,35060],[19716,56594,13084],[19716,56594,5010],[14368,56533,5088],[14368,56533,23666],[10161,14159,14081],[7281,55591,7611],[7281,55591,11724],[36874,21636,23843],[36874,21636,23657],[36933,21641,23655],[36933,21641,23842],[36874,21636,23424],[36910,21306,22872],[36910,21306,23119],[36985,21704,23750],[32139,8878,14017],[32139,8878,7289],[32184,8333,7920],[32184,8333,14084],[50634,40870,19695],[50634,40870,19656],[50634,40870,14614],[48704,40782,18811],[48704,40782,23157],[50634,40870,14577],[49301,42258,16817],[49301,42258,17217],[51223,40208,19820],[51223,40208,19691],[51349,40096,19849],[56508,38482,26525],[56508,38482,27810],[56175,38035,27153],[56043,37859,26892],[56043,37859,27401],[56192,35593,27124],[56192,35593,26931],[56192,35593,26196],[56192,35593,26162],[56682,35590,26203],[56686,35590,26204],[56686,35590,26195],[55605,41095,26424],[55605,41095,14450],[51969,40930,14543],[51969,40930,19729],[56224,34951,26923],[25035,45485,22638],[8322,50723,12519],[8322,50723,12451],[8322,50723,10230],[8651,49115,7349],[8651,49115,15186],[20134,50402,13987],[20134,50402,13402],[19998,50221,13704],[50913,40556,19205],[50913,40556,19733],[50653,40848,19661],[24186,61861,4858],[63503,35407,26851],[63503,35407,26979],[57722,35074,26981],[56273,34374,26953],[56273,34374,26121],[56226,34926,26925],[56723,34424,26146],[62132,29415,20014],[62132,29415,14464],[62284,29709,20369],[62284,29709,14026],[35228,21631,23614],[35228,21631,23683],[3476,49459,7699],[3476,49459,8627],[60887,41334,18774],[60887,41334,14316],[46018,46409,23608],[46018,46409,23445],[46113,46346,23341],[46113,46346,23443],[45972,46697,22927],[51495,28663,19781],[51495,28663,18739],[51495,28663,14530],[51932,28694,14527],[51932,28694,19748],[47664,33353,35275],[47664,33353,25488],[48117,34020,26428],[48117,34020,27870],[48117,34020,35261],[51523,28477,18745],[51523,28477,14817],[62852,41423,22573],[62852,41423,15151],[62852,41423,14266],[32809,3386,6858],[61595,29777,18902],[61595,29777,13851],[33023,8055,15642],[33023,8055,6919],[34176,8267,6694],[34176,8267,17805],[34139,8380,6541],[34139,8380,17740],[32184,8333,5771],[32711,8346,6515],[32711,8346,15069],[10285,36104,36610],[10285,36104,21713],[10821,35584,22594],[10821,35584,36626],[9731,35007,38059],[9731,35007,23416],[10660,33536,43895],[10660,33536,25855],[9731,35007,43888],[36033,63254,10130],[25702,60555,8784],[25702,60555,4857],[28390,56933,4878],[28390,56933,8187],[45692,34602,34777],[45692,34602,27417],[45086,34547,27448],[45086,34547,34683],[50308,26763,18878],[50308,26763,17331],[28404,62131,1575],[28981,62202,6413],[28545,62416,1305],[26546,8463,22994],[26585,7927,6039],[26585,7927,23034],[47293,34916,26618],[47293,34916,35003],[27150,8750,24035],[27135,9141,23757],[27135,9141,24028],[27053,9272,23895],[27203,7343,7088],[27203,7343,8313],[27203,7343,24059],[29840,4848,11567],[30113,4589,11903],[30233,4723,11768],[2018,32697,43796],[2018,32697,38810],[3337,30039,37749],[3337,30039,43805],[56546,30232,27025],[56546,30232,20944],[56727,30256,20988],[56727,30256,26700],[16572,44523,23363],[16572,44523,23354],[17547,44588,23361],[86,31619,36245],[1069,30257,9920],[1069,30257,35966],[25325,39364,14675],[25325,39364,17901],[26468,39244,15143],[26468,39244,14385],[20267,50990,14319],[20267,50990,13031],[19969,50571,13696],[24705,56650,23529],[24705,56650,4936],[19716,56594,14032],[5595,38667,8559],[3961,38586,8713],[5595,38667,38083],[3868,38049,9941],[3868,38049,37072],[46351,46359,23338],[46351,46359,23202],[46248,46354,23200],[46248,46354,23339],[52008,1662,17346],[52008,1662,9849],[56121,47504,23521],[56121,47504,22359],[56866,47564,22342],[50887,60371,13441],[50887,60371,14122],[34055,62616,6836],[33968,62659,6705],[50651,60353,13866],[50651,60353,14093],[45813,33807,26268],[45813,33807,34904],[31661,60996,9851],[31661,60996,10108],[31791,60885,9951],[46342,33269,26940],[46342,33269,35065],[31463,61165,9699],[31463,61165,9841],[46615,33299,27321],[46615,33299,35107],[47293,34916,25887],[48024,36498,24522],[46462,34828,27378],[46462,34828,34875],[44346,27183,18834],[44346,27183,19182],[25408,45497,22660],[25408,45497,23350],[30026,58377,15190],[30026,58377,7852],[29519,57394,7951],[29519,57394,13955],[31235,57302,17472],[31235,57302,6701],[30846,57647,6585],[30846,57647,16739],[65057,35577,26932],[65057,35577,26915],[65057,35577,26887],[65057,35577,26870],[64546,35521,26906],[10212,36710,20737],[10212,36710,36128],[202,31621,36346],[38481,33869,27615],[1631,27590,8904],[2944,27656,8958],[33638,62823,1453],[33638,62823,7123],[38159,57452,16694],[34562,56890,7473],[34562,56890,22939],[33388,62757,7472],[33388,62757,6975],[33429,62353,7485],[16175,20318,23747],[32139,8878,14650],[32296,9275,14326],[36910,21306,23369],[36654,21298,22889],[37471,9564,24010],[37471,9564,23834],[37006,21310,23391],[37006,21310,23549],[4661,30666,45105],[4661,30666,43821],[6394,29333,43838],[6394,29333,44891],[6394,29333,45058],[4758,36259,53120],[4758,36259,43835],[4120,35244,43825],[4120,35244,53498],[41490,27493,19603],[42015,27516,15322],[42015,27516,20331],[64615,48176,22170],[64615,48176,23260],[64002,48122,22193],[32258,7042,7323],[32258,7042,9411],[31406,7311,8976],[40400,4757,11677],[40400,4757,10304],[40615,4671,10674],[40615,4671,11353],[40615,4671,11823],[49064,28491,24039],[49064,28491,18876],[9267,44078,7536],[16403,44514,23358],[1455,27588,8903],[43712,64177,4960],[44816,63935,23993],[44816,63935,5355],[43712,64177,21967],[45745,46679,22933],[25719,45507,22678],[60431,29892,20249],[60431,29892,20169],[60380,29897,20259],[36775,21713,23564],[36775,21713,23714],[26456,20959,23815],[26456,20959,23424],[21431,8096,14266],[51877,41484,15395],[51877,41484,19617],[32676,5803,6704],[32676,5803,6151],[32538,6953,6099],[32538,6953,6779],[33051,5779,5960],[33051,5779,6676],[32839,5793,6380],[32258,7042,5715],[35678,38829,15655],[35678,38829,16109],[35000,39824,15490],[35000,39824,15261],[35228,39490,15546],[41025,40775,15660],[41025,40775,15341],[6912,49280,7557],[9301,35688,37810],[9301,35688,43885],[13586,37029,20540],[13586,37029,12229],[10780,37347,12978],[10780,37347,19770],[13788,39021,17374],[13788,39021,15242],[35175,9183,19705],[35175,9183,6609],[37481,9308,6656],[37481,9308,24020],[37006,21310,23109],[37157,21315,23095],[42644,9586,14544],[33843,58284,6446],[33843,58284,7616],[33749,59208,7586],[4959,18047,12931],[9898,19444,13415],[5007,17099,13951],[38682,5828,6461],[38682,5828,9097],[38422,8254,6606],[7929,36722,44902],[7929,36722,43872],[5467,36363,43843],[5467,36363,45204],[18611,32926,27572],[63414,42074,15317],[63414,42074,15203],[64504,41498,25767],[64504,41498,15137],[34646,57506,6868],[34646,57506,6704],[34475,57451,6718],[34475,57451,6904],[34533,58368,6433],[34533,58368,6669],[34533,58368,5996],[34615,57740,6631],[36480,58603,9828],[36480,58603,5972],[36528,58105,6474],[36528,58105,9796],[37918,58548,6184],[37918,58548,7428],[38962,5818,9888],[38962,5818,9148],[38682,5828,9450],[15484,41754,19242],[15484,41754,13161],[17201,40216,15781],[17201,40216,16738],[21054,37936,19787],[21054,37936,12903],[19529,39972,16147],[19529,39972,16389],[19060,40021,16266],[21887,39725,17004],[21887,39725,15548],[47604,33409,35257],[47604,33409,25580],[3175,33743,45201],[3175,33743,43811],[2476,32631,43801],[2476,32631,46582],[3134,33794,53949],[3134,33794,43811],[3175,33743,54056],[3411,35403,43818],[3411,35403,52478],[3563,33268,45890],[3563,33268,55049],[5674,33949,61945],[5674,33949,56975],[5674,33949,56796],[5540,33380,57273],[5540,33380,61939],[74197,43617,14465],[74197,43617,13940],[74554,43605,13935],[37165,63723,12234],[6553,16759,16664],[6553,16759,17221],[6155,19049,13287],[70442,28314,13835],[70442,28314,17011],[74513,32785,18618],[74513,32785,13243],[75256,33798,13402],[75256,33798,18711],[46170,46349,23340],[42868,27553,16611],[42868,27553,21515],[49071,25602,18991],[49039,28835,24012],[49039,28835,18864],[47099,28432,27463],[47099,28432,18984],[75925,39405,15582],[75926,39658,15819],[74913,39157,14168],[65057,35561,26909],[65061,35352,26606],[65061,35352,27201],[70613,35070,24837],[70613,35070,25366],[70575,33982,23918],[41025,40775,19322],[40971,40628,15677],[40971,40628,19407],[41202,40244,15739],[41202,40244,19732],[5232,55386,7249],[68926,53371,11766],[68808,55883,18867],[68808,55883,19207],[48559,40123,23440],[48559,40123,18795],[47540,40090,18816],[47540,40090,25267],[48589,40777,23363],[48589,40777,18813],[46290,40207,18845],[46290,40207,27503],[9662,17709,19413],[9662,17709,16283],[9386,17625,16366],[9386,17625,19298],[8981,16669,17886],[9898,19444,21999],[73917,31013,13928],[66295,37632,24080],[66295,37632,24649],[66295,37632,29281],[65259,39061,27259],[66988,60086,19315],[66988,60086,20192],[67893,60203,19289],[9078,35437,38222],[8000,36733,37967],[8000,36733,44893],[13304,34243,27099],[13304,34243,24970],[13197,34167,25082],[13197,34167,27374],[11596,32411,27741],[11596,32411,31578],[13136,32496,27748],[5540,33380,56978],[5278,33365,56979],[3563,33268,56988],[5340,31092,49047],[5340,31092,54014],[5340,31092,56987],[5833,33397,56976],[5833,33397,61949],[5680,33580,56976],[5680,33580,61944],[8109,34351,50772],[8109,34351,43869],[8412,34690,50109],[42291,8723,13871],[42291,8723,6701],[42421,7047,8415],[42421,7047,13992],[42096,39915,20204],[42096,39915,19582],[41979,40526,18752],[41979,40526,19757],[42096,39915,21075],[41889,39908,20152],[41889,39908,20759],[40545,39471,20075],[40545,39471,15809],[40199,38533,15916],[40199,38533,20617],[33368,62957,1289],[33388,62757,1491],[33368,62957,6722],[33661,62678,1599],[33661,62678,7117],[27300,59181,8435],[10472,37002,12481],[10472,37002,20294],[13511,36289,21718],[13586,37029,19770],[65058,35130,26285],[65058,35130,26867],[65058,35130,26905],[64764,35074,26234],[65067,35119,26887],[204,38443,33440],[204,38443,8963],[1913,37500,11157],[1913,37500,35796],[68263,35899,27093],[68263,35899,26572],[66732,35538,27011],[53401,59694,19526],[53554,58534,19674],[46462,34828,27127],[52626,29142,19714],[52626,29142,13909],[53057,29259,13773],[53057,29259,20517],[8620,52538,13317],[8620,52538,11238],[8123,51694,11235],[8123,51694,12321],[8417,49143,7388],[8417,49143,15121],[51561,41465,19600],[51561,41465,15396],[51154,40893,14564],[51154,40893,19684],[48659,42278,16907],[48659,42278,18604],[48659,42278,18854],[48624,41522,18834],[48702,43209,18320],[48702,43209,18880],[56605,29326,19185],[56605,29326,14033],[56978,29050,14496],[56978,29050,18647],[52152,29013,19746],[52152,29013,14058],[13586,37029,14527],[15589,36802,21090],[70417,47909,23255],[69161,48332,22409],[70335,41762,18502],[70335,41762,13317],[70207,41756,13245],[70207,41756,14078],[70207,41756,18505],[43067,39946,22560],[43067,39946,19594],[74971,30889,15479],[48211,21890,21918],[39498,59048,13248],[39498,59048,9099],[50572,59062,14140],[55777,35595,26170],[28458,56693,15418],[28458,56693,4881],[29552,57084,7942],[28491,56946,8165],[28491,56946,15315],[28491,56946,4876],[39984,51165,14266],[67312,42209,15273],[67312,42209,15041],[38671,59603,8823],[38671,59603,6066],[40156,57764,14272],[14209,28179,25663],[14209,28179,25069],[14041,28653,25497],[14041,28653,26016],[15642,28930,21859],[15642,28930,22008],[11787,20692,23784],[11787,20692,23439],[11787,20692,22721],[10661,23741,17912],[10661,23741,18406],[12366,25873,15060],[12366,25873,14805],[12485,26022,14826],[48402,43569,18896],[8206,51290,12375],[8206,51290,11245],[8375,50714,12538],[70606,36058,26447],[70606,36058,24726],[70381,36591,24956],[70381,36591,25697],[70283,41621,18625],[70283,41621,18695],[70099,41117,19390],[69130,41664,18588],[69130,41664,19180],[69072,41212,19215],[69130,41664,14040],[67027,41720,14324],[67027,41720,15145],[67027,41720,23881],[5618,37270,43847],[5618,37270,39322],[1563,35916,36864],[1563,35916,43798],[6036,35477,54714],[6036,35477,45273],[5936,35055,45343],[5936,35055,54987],[5936,35055,55869],[5467,36363,53846],[61219,61163,19244],[3961,38586,36687],[1471,38461,34561],[5947,38685,43854],[5947,38685,35261],[5595,38667,43850],[37471,9564,6671],[38294,9452,6678],[38294,9452,22235],[41671,5549,9898],[41671,5549,10371],[41621,5746,12505],[41621,5746,10042],[41671,5549,12582],[38195,5845,6454],[38195,5845,8687],[35751,2390,4978],[38982,2576,6064],[38291,5448,8757],[38291,5448,10670],[38549,4375,8949],[38982,2576,9269],[13218,33773,25714],[13218,33773,27374],[13321,33757,27121],[13321,33757,25749],[13843,33738,25828],[19600,32968,27583],[19600,32968,27597],[20413,32865,27391],[20413,32865,27836],[71210,28280,15016],[71210,28280,17142],[71749,29016,16065],[70025,29559,15052],[70025,29559,18135],[72502,30552,13778],[72502,30552,18815],[73739,30485,14009],[73739,30485,13519],[73502,30751,13574],[73370,30900,13606],[73370,30900,13332],[73370,30900,18187],[73931,30494,13756],[48079,3908,23705],[48079,3908,3558],[48568,3972,3474],[48568,3972,22883],[48185,1498,9403],[52008,1662,9565],[75256,33798,18888],[75403,33998,13434],[75403,33998,18671],[75464,34064,18583],[75464,34064,14433],[75740,34458,14407],[75740,34458,18172],[76036,34397,14612],[75908,34423,14406],[75740,34458,14136],[77986,36172,17621],[77986,36172,17795],[76866,37148,15775],[76360,37588,14862],[76360,37588,14941],[75302,37045,14275],[75302,37045,13284],[75918,37361,14248],[76360,37588,14228],[76019,34517,14578],[81299,39690,14500],[81053,39998,13920],[35182,5264,9618],[35583,5672,10234],[35583,5672,10392],[35644,5615,10313],[35444,8501,6571],[35444,8501,6445],[35480,8412,6566],[36206,6306,9417],[36206,6306,6448],[37406,7528,6544],[37406,7528,7844],[37164,8452,6597],[65348,40705,27409],[65348,40705,14903],[65361,40949,14969],[65361,40949,27431],[65723,40717,26574],[65723,40717,14898],[66275,40433,14810],[66275,40433,25285],[66126,40459,25622],[66126,40459,14820],[29139,62313,6645],[47452,22731,23244],[47452,22731,23202],[48220,22801,23216],[34475,57451,24175],[33648,57188,6784],[33648,57188,22420],[34483,56878,23076],[34483,56878,24081],[34562,56890,24246],[9286,39269,7535],[9286,39269,16044],[14636,41716,19265],[14636,41716,13142],[1455,27588,16301],[2387,28429,9810],[2387,28429,17924],[12975,40759,17965],[12975,40759,14518],[13477,39685,16282],[60431,29892,13556],[61077,29828,13720],[61077,29828,19031],[2220,34940,38304],[2220,34940,43803],[1211,34322,37938],[1211,34322,43791],[6601,37624,43859],[6601,37624,35730],[6359,38705,35251],[7037,37891,35611],[7037,37891,10367],[7899,38419,35375],[7899,38419,9174],[7862,36899,43871],[7862,36899,37934],[7862,36899,36049],[7037,37891,43864],[14849,26371,23439],[14849,26371,14949],[14907,26374,14951],[14907,26374,23305],[8921,39045,7761],[9286,39269,7256],[9139,39179,7459],[9288,38680,8604],[9288,38680,15148],[14786,30320,23945],[15846,27767,15143],[15846,27767,21286],[14209,28179,15132],[56605,29326,27041],[60878,29332,14463],[60878,29332,19159],[57318,58033,20454],[57318,58033,20888],[56906,57810,21128],[57318,58033,19695],[57301,58362,19652],[57301,58362,20444],[57584,58756,19598],[57584,58756,19955],[3878,29773,43811],[3878,29773,37997],[3878,29773,37703],[5730,28862,36262],[5730,28862,43830],[9471,27982,37438],[8233,27920,40518],[8233,27920,34746],[6635,29505,56987],[6635,29505,45102],[6635,29505,44269],[7127,29854,43003],[7127,29854,43847],[7127,29854,56984],[9033,33625,56960],[9033,33625,43877],[8982,33766,43877],[8982,33766,56959],[11272,32800,32329],[11272,32800,43901],[11272,32800,27090],[1211,34322,10185],[102,38442,8830],[149,35121,8827],[31220,63780,6388],[31129,63716,6388],[31129,63716,6503],[31567,62059,8550],[31567,62059,8503],[31441,62537,7927],[42470,27106,15999],[42470,27106,19286],[41176,28269,19252],[41176,28269,17270],[64615,48176,23839],[65250,48231,22147],[41176,28269,19259],[40778,29241,20709],[40778,29241,19161],[39501,29223,20914],[39501,29223,19137],[41576,27284,19327],[41576,27284,15633],[41954,26356,17101],[41954,26356,19343],[42141,27402,19292],[42141,27402,15510],[42421,38088,21536],[42421,38088,21440],[42349,38337,21347],[42508,38071,21572],[72971,28202,17726],[72971,28202,17444],[72765,28211,17408],[72943,28116,17573],[72943,28116,17657],[73129,27671,17817],[40315,4686,11793],[40315,4686,10150],[40379,2657,10132],[42413,6548,13945],[42413,6548,8918],[42225,6276,9165],[42225,6276,13598],[30522,59389,7755],[30522,59389,8340],[30522,59389,8385],[30123,59670,7842],[30026,58377,7189],[30289,58915,7801],[33136,57548,6661],[33136,57548,21436],[35000,39824,14461],[34870,40014,14764],[34870,40014,15098],[36608,40585,15468],[34883,8397,6555],[34883,8397,19131],[71277,41591,18000],[71277,41591,13524],[70283,41621,13079],[72204,44061,17732],[72911,42052,15033],[72911,42052,14046],[74079,40984,14884],[74079,40984,14084],[74606,39530,14153],[74528,38923,19221],[74528,38923,14187],[73823,39459,14170],[73823,39459,19011],[78064,36238,17742],[73451,40517,14120],[73451,40517,17914],[7308,36479,52001],[7308,36479,45000],[10472,37002,35647],[9579,38269,14494],[9579,38269,35262],[9579,38269,9553],[8738,37606,11054],[8738,37606,35734],[8738,37606,36564],[75287,37649,18380],[75287,37649,14243],[74938,38612,14197],[74938,38612,18720],[36127,5585,10378],[36127,5585,6402],[38291,5448,6431],[40379,2657,11486],[72169,41150,17965],[72169,41150,14107],[72169,41150,13296],[2917,28907,36447],[2387,28429,35592],[2387,28429,35755],[2944,27656,34575],[2996,31945,47572],[2996,31945,45150],[4661,30666,49517],[5109,30884,45173],[5109,30884,49291],[216,30671,8824],[261,27570,8820],[67405,16037,4689],[67601,10465,4476],[67882,10480,4466],[67405,16037,11459],[69635,38362,23202],[4519,50650,9899],[41824,40976,19408],[41824,40976,15571],[41188,41218,15999],[41188,41218,19067],[41979,40526,15740],[41899,40948,15680],[41899,40948,19447],[44066,40416,24121],[44066,40416,18898],[43351,40483,17820],[43351,40483,23033],[42172,40844,18332],[42172,40844,16080],[43313,40411,17752],[43313,40411,18980],[43313,40411,22970],[69179,41710,18527],[69179,41710,14104],[70353,42622,14632],[70353,42622,15382],[70727,43734,17039],[70727,43734,20495],[69234,43784,17257],[72503,41557,14079],[70785,42958,15355],[70785,42958,15853],[70727,43734,15333],[31463,61165,9489],[31299,61306,9221],[31299,61306,9620],[31619,61468,9252],[31619,61468,9251],[31008,61154,9198],[31618,61482,9235],[55600,47463,22371],[58625,59183,19530],[30846,57647,6165],[31520,58003,6487],[70510,46649,25376],[70510,46649,15249],[69000,47560,12909],[70437,47636,15221],[70437,47636,23714],[5007,17099,14172],[5162,17124,14348],[6553,16759,17518],[6047,16605,16180],[6579,16606,17484],[64203,56800,23783],[64203,56800,23292],[63247,57494,22228],[67155,59523,20090],[64565,56537,24371],[64565,56537,22965],[64600,50614,23584],[57721,50211,20700],[68926,53371,13945],[68366,53777,13203],[45496,34744,27171],[45496,34744,34725],[53666,28816,14516],[53666,28816,21590],[56625,29025,14498],[56625,29025,27046],[70606,36058,26692],[70644,35967,26560],[70644,35967,24687],[81053,39998,14021],[80410,39668,14049],[68511,55825,19165],[75365,34660,18641],[75365,34660,13525],[75302,37045,18433],[8761,53566,13761],[8761,53566,10667],[8258,53362,10776],[8258,53362,12887],[8396,53048,11222],[8396,53048,13051],[8438,55926,13710],[8438,55926,7533],[61610,58662,19562],[61471,59548,19450],[5936,35055,56969],[6021,34819,56970],[6021,34819,54858],[8109,34351,56961],[58005,40486,14995],[58005,40486,23707],[56508,38482,16667],[73871,41560,14056],[79347,29592,19007],[76182,33388,14457],[76182,33388,15021],[76355,32189,16276],[34076,32121,24808],[34076,32121,25371],[37219,30442,23053],[37219,30442,22523],[37219,30442,21523],[1432,33147,38676],[1432,33147,10449],[180,33076,37533],[483,33876,43781],[483,33876,9234],[1432,33147,43791],[2996,31945,43805],[4154,30419,43815],[4154,30419,45027],[6394,29333,36939],[66422,37457,24329],[7929,36722,38035],[8703,34218,43875],[8703,34218,56959],[37184,63305,11731],[37184,63305,8198],[37931,61444,7128],[37931,61444,10268],[39266,62816,13568],[39266,62816,13425],[38967,63668,13003],[38967,63668,14288],[39274,62690,13419],[44159,25973,19240],[44159,25973,17916],[44332,25616,18483],[44332,25616,19245],[45273,25206,19211],[322,27571,8900],[859,29915,9642],[859,29915,35501],[1522,29628,10494],[1522,29628,35838],[16222,27988,20446],[16222,27988,15181],[17158,26468,18166],[42049,4093,13150],[42049,4093,9192],[40825,4586,11037],[43408,42839,18257],[43408,42839,18980],[43837,43344,18986],[44002,41242,18923],[1750,29312,35773],[1750,29312,10783],[1664,29208,10671],[1664,29208,19442],[1664,29208,35613],[322,27571,16278],[1783,29266,35764],[1783,29266,19553],[5109,30884,54058],[5396,31023,45217],[5396,31023,54071],[5396,31023,56987],[41621,5746,9612],[35444,8501,20184],[71683,34818,23461],[75419,34525,18586],[75419,34525,13619],[75464,34064,13462],[75905,33468,14572],[75905,33468,14457],[75858,33531,14455],[38650,61237,5945],[38650,61237,10855],[29438,50604,14032],[21266,15007,14308],[41680,16589,14779],[42549,16705,14833],[51302,29945,19857],[76382,41281,17858],[52109,40091,19899],[52601,29999,19757],[50784,51855,14487],[51737,51845,14609],[40759,51179,14332],[6703,16487,17709],[32080,15694,14144],[13586,37029,26027],[5454,51321,11157],[5661,52458,11145],[55513,55867,23537],[63137,48893,20806],[76809,31057,17858]],"CityObjects":{"NL.IMBAG.Pand.0363100012178469":{"type":"Building","attributes":{"b3_bag_bag_overlap":0.0,"b3_bouwlagen":4,"b3_dak_type":"slanted","b3_extrusie":"standard","b3_h_dak_50p":19.957000732421875,"b3_h_dak_70p":21.899999618530273,"b3_h_dak_max":36.62099838256836,"b3_h_dak_min":4.909999847412109,"b3_h_maaiveld":1.1339999437332153,"b3_h_nok":45.603050231933594,"b3_is_glas_dak":false,"b3_kas_warenhuis":false,"b3_kwaliteitsindicator":false,"b3_mutatie_ahn3_ahn4":false,"b3_mutatie_ahn4_ahn5":false,"b3_n_nok":90,"b3_n_vlakken":239,"b3_nodata_fractie_ahn3":0.005787738133221865,"b3_nodata_fractie_ahn4":0.052291542291641235,"b3_nodata_fractie_ahn5":0.0334477424621582,"b3_nodata_radius_ahn3":0.12066808342933655,"b3_nodata_radius_ahn4":0.0027194751892238855,"b3_nodata_radius_ahn5":0.0,"b3_opp_buitenmuur":6440.75,"b3_opp_dak_plat":200.76,"b3_opp_dak_schuin":6836.25,"b3_opp_grond":3712.65,"b3_opp_scheidingsmuur":1308.18,"b3_puntdichtheid_ahn3":19.848913192749023,"b3_puntdichtheid_ahn4":48.224117279052734,"b3_puntdichtheid_ahn5":19.94652557373047,"b3_pw_bron":"ahn5","b3_pw_datum":2023,"b3_pw_onvoldoende":false,"b3_pw_selectie_reden":"PREFERRED_AND_LATEST","b3_rmse_lod12":6.978324890136719,"b3_rmse_lod13":2.2730486392974854,"b3_rmse_lod22":0.6088934540748596,"b3_t_run":10208,"b3_val3dity_lod12":"[]","b3_val3dity_lod13":"[104]","b3_val3dity_lod22":"[102,104,203]","b3_volume_lod12":76630.6015625,"b3_volume_lod13":74041.015625,"b3_volume_lod22":67988.84375,"begingeldigheid":"2023-09-28","documentdatum":"2023-09-28","documentnummer":"GV00007182_AC00AC","eindgeldigheid":null,"eindregistratie":null,"fid":8473678,"geconstateerd":false,"identificatie":"NL.IMBAG.Pand.0363100012178469","oorspronkelijkbouwjaar":1300,"status":"Pand in gebruik","tijdstipeindregistratielv":null,"tijdstipinactief":null,"tijdstipinactieflv":null,"tijdstipnietbaglv":null,"tijdstipregistratie":"2023-09-28T18:55:25Z","tijdstipregistratielv":"2023-09-28T21:49:20Z","voorkomenidentificatie":3},"geometry":[{"boundaries":[[[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104]]],"lod":"0","type":"MultiSurface"}]}}}]}
\ No newline at end of file
diff --git a/web-standalone/tests/test_bag3d.py b/web-standalone/tests/test_bag3d.py
new file mode 100644
index 0000000..bc5a8ab
--- /dev/null
+++ b/web-standalone/tests/test_bag3d.py
@@ -0,0 +1,95 @@
+"""Run with: python3 -m pytest web-standalone/tests"""
+import json
+from pathlib import Path
+import sys
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
+
+import bag3d # noqa: E402
+
+FIXTURE = json.loads((Path(__file__).parent / "fixtures" / "bag3d-pand.json").read_text(encoding="utf-8"))
+
+
+def test_rd_conversion_against_two_church_towers():
+ # Westertoren, Amsterdam: RD (120700.7, 487525.5); Martinitoren, Groningen: RD (233883.1, 582065.2).
+ for (lat, lon), (rx, ry) in (((52.37453, 4.88352), (120700.7, 487525.5)), ((53.21938, 6.56820), (233883.1, 582065.2))):
+ x, y = bag3d.wgs84_to_rd(lat, lon)
+ assert abs(x - rx) < 1 and abs(y - ry) < 1
+ back_lat, back_lon = bag3d.rd_to_wgs84(rx, ry)
+ assert abs(back_lat - lat) < 1e-5 and abs(back_lon - lon) < 1e-5
+
+
+def test_bbox_is_rd_metres_around_the_point():
+ parts = [float(p) for p in bag3d.bbox_around(52.3731, 4.8932, 12).split(",")]
+ assert parts[2] - parts[0] == 24 and parts[3] - parts[1] == 24
+
+
+def test_buildings_carry_heights_and_a_footprint_in_metres():
+ b = bag3d.buildings(FIXTURE)
+ assert len(b) == 1
+ building = b[0]
+ assert building["id"] == "NL.IMBAG.Pand.0363100012178469"
+ assert round(building["roof_m"], 2) == round(36.62099838256836 - 1.1339999437332153, 2)
+ ring = building["surfaces"][0][0]
+ assert len(ring) == 105
+ xs = [p[0] for p in ring]
+ ys = [p[1] for p in ring]
+ # The footprint sits where the transform puts it, in RD metres, not in scaled ints.
+ assert 121600 < min(xs) < max(xs) < 121800
+ assert 487400 < min(ys) < max(ys) < 487600
+
+
+def test_point_inside_and_outside_the_footprint():
+ building = bag3d.buildings(FIXTURE)[0]
+ ring = building["surfaces"][0][0]
+ cx = sum(p[0] for p in ring) / len(ring)
+ cy = sum(p[1] for p in ring) / len(ring)
+ assert bag3d.point_in_ring(cx, cy, ring)
+ assert not bag3d.point_in_ring(cx + 500, cy, ring)
+ lat, lon = bag3d.rd_to_wgs84(cx, cy)
+ inside = bag3d.roof_for_point(FIXTURE, lat, lon)
+ assert inside["building"] == building["id"]
+ assert inside["roof_m"] == 35.5
+ assert inside["distance_m"] == 0.0
+ assert inside["buildings"] == 1
+ lat2, lon2 = bag3d.rd_to_wgs84(cx + 500, cy)
+ outside = bag3d.roof_for_point(FIXTURE, lat2, lon2)
+ assert outside["building"] is None and outside["roof_m"] is None and outside["distance_m"] is None
+
+
+def test_the_tallest_building_within_the_radius_counts_with_its_distance():
+ building = bag3d.buildings(FIXTURE)[0]
+ ring = building["surfaces"][0][0]
+ east = max(p[0] for p in ring)
+ cy = sum(p[1] for p in ring) / len(ring)
+ # 40 m east of the footprint's eastern edge: inside a 100 m radius, outside a 10 m one.
+ x, y = east + 40, cy
+ d = bag3d.footprint_distance(x, y, building["surfaces"])
+ assert 35 < d <= 40.5
+ lat, lon = bag3d.rd_to_wgs84(x, y)
+ near = bag3d.roof_for_point(FIXTURE, lat, lon, radius_m=100)
+ assert near["building"] == building["id"] and near["roof_m"] == 35.5
+ assert 35 < near["distance_m"] <= 40.5
+ far = bag3d.roof_for_point(FIXTURE, lat, lon, radius_m=10)
+ assert far["building"] is None
+
+
+def test_pages_are_searched_together_and_the_taller_one_wins():
+ page2 = json.loads(json.dumps(FIXTURE))
+ obj = page2["features"][0]["CityObjects"]
+ key = next(iter(obj))
+ obj[key]["attributes"]["b3_h_dak_max"] = 60.0
+ obj["NL.IMBAG.Pand.taller"] = obj.pop(key)
+ building = bag3d.buildings(FIXTURE)[0]
+ ring = building["surfaces"][0][0]
+ cx = sum(p[0] for p in ring) / len(ring)
+ cy = sum(p[1] for p in ring) / len(ring)
+ lat, lon = bag3d.rd_to_wgs84(cx, cy)
+ result = bag3d.roof_for_point([FIXTURE, page2], lat, lon)
+ assert result["building"] == "NL.IMBAG.Pand.taller"
+ assert result["roof_m"] == round(60.0 - 1.1339999437332153, 1)
+ assert result["buildings"] == 2
+
+
+def test_empty_response_means_no_building():
+ assert bag3d.roof_for_point({"features": []}, 52.0, 5.0) == {"roof_m": None, "building": None, "distance_m": None, "buildings": 0}