Add crosswalk and intersection surface rendering
This commit is contained in:
@@ -113,14 +113,16 @@ node scripts/build-osm2streets-qgis.js --arrow-scale 0.8
|
||||
GeoPackage 内会生成:
|
||||
|
||||
- `road_surface`
|
||||
- `intersection_surface`
|
||||
- `sidewalks`
|
||||
- `sidewalk_corners`
|
||||
- `lane_separators`
|
||||
- `center_lines`
|
||||
- `vehicle_stop_lines`
|
||||
- `lane_arrows_webscale`
|
||||
- `crosswalks`
|
||||
|
||||
QGIS 工程的绘制顺序已经固定为:路面在底,箭头、停止线、中心线在上。
|
||||
QGIS 工程的绘制顺序已经固定为:路段和路口路面在底,车道线、斑马线、停止线和箭头在上。
|
||||
|
||||
## 注意
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ fs.mkdirSync(path.dirname(previewPath), { recursive: true });
|
||||
|
||||
const xml = fs.readFileSync(inputPath, "utf8");
|
||||
const bbox = getOsmBounds(xml);
|
||||
const osm = parseOsm(xml);
|
||||
const clip = makeClipPolygon(bbox, clipPad);
|
||||
|
||||
const network = new JsStreetNetwork(xml, JSON.stringify(clip), config.osm2streets);
|
||||
@@ -58,30 +59,35 @@ writeGeoJson(outDir, "lane_markings.geojson", network.toLaneMarkingsGeojson());
|
||||
writeGeoJson(outDir, "intersection_markings.geojson", network.toIntersectionMarkingsGeojson());
|
||||
fs.writeFileSync(path.join(outDir, "network.json"), network.toJson());
|
||||
|
||||
const split = splitLayers(outDir, arrowScale);
|
||||
const split = splitLayers(outDir, arrowScale, osm);
|
||||
writeJson(path.join(outDir, "road_surface.geojson"), split.roadSurface);
|
||||
writeJson(path.join(outDir, "intersection_surface.geojson"), split.intersectionSurface);
|
||||
writeJson(path.join(outDir, "sidewalks.geojson"), split.sidewalks);
|
||||
writeJson(path.join(outDir, "lane_separators.geojson"), split.laneSeparators);
|
||||
writeJson(path.join(outDir, "center_lines.geojson"), split.centerLines);
|
||||
writeJson(path.join(outDir, "vehicle_stop_lines.geojson"), split.vehicleStopLines);
|
||||
writeJson(path.join(outDir, "lane_arrows_webscale.geojson"), split.laneArrows);
|
||||
writeJson(path.join(outDir, "sidewalk_corners.geojson"), split.sidewalkCorners);
|
||||
writeJson(path.join(outDir, "crosswalks.geojson"), split.crosswalks);
|
||||
|
||||
if (fs.existsSync(gpkgPath)) {
|
||||
fs.unlinkSync(gpkgPath);
|
||||
}
|
||||
const ogrEnv = qgisEnv();
|
||||
importLayer(gpkgPath, path.join(outDir, "road_surface.geojson"), "road_surface", false, ogrEnv);
|
||||
importLayer(gpkgPath, path.join(outDir, "intersection_surface.geojson"), "intersection_surface", true, ogrEnv);
|
||||
importLayer(gpkgPath, path.join(outDir, "sidewalks.geojson"), "sidewalks", true, ogrEnv);
|
||||
importLayer(gpkgPath, path.join(outDir, "sidewalk_corners.geojson"), "sidewalk_corners", true, ogrEnv);
|
||||
importLayer(gpkgPath, path.join(outDir, "lane_separators.geojson"), "lane_separators", true, ogrEnv);
|
||||
importLayer(gpkgPath, path.join(outDir, "center_lines.geojson"), "center_lines", true, ogrEnv);
|
||||
importLayer(gpkgPath, path.join(outDir, "vehicle_stop_lines.geojson"), "vehicle_stop_lines", true, ogrEnv);
|
||||
importLayer(gpkgPath, path.join(outDir, "lane_arrows_webscale.geojson"), "lane_arrows_webscale", true, ogrEnv);
|
||||
importLayer(gpkgPath, path.join(outDir, "crosswalks.geojson"), "crosswalks", true, ogrEnv);
|
||||
|
||||
const qgisScript = path.join(outDir, "_create_qgis_project.py");
|
||||
const previewFeature = split.crosswalks.features[0] || split.laneArrows.features[0] || split.roadSurface.features[0];
|
||||
const defaultPreviewExtent = extentString(expandBounds(
|
||||
featureBounds(split.laneArrows.features[0] || split.roadSurface.features[0]),
|
||||
featureBounds(previewFeature),
|
||||
previewPad,
|
||||
));
|
||||
fs.writeFileSync(qgisScript, makeQgisScript({
|
||||
@@ -223,6 +229,56 @@ function getOsmBounds(xmlText) {
|
||||
return { minLon, minLat, maxLon, maxLat };
|
||||
}
|
||||
|
||||
function parseOsm(xmlText) {
|
||||
const nodes = new Map();
|
||||
const ways = new Map();
|
||||
for (const match of xmlText.matchAll(/<node\b([^>]*?)(?:\/>|>([\s\S]*?)<\/node>)/g)) {
|
||||
const attrs = parseAttrs(match[1]);
|
||||
const id = Number(attrs.id);
|
||||
const lat = Number(attrs.lat);
|
||||
const lon = Number(attrs.lon);
|
||||
if (!Number.isFinite(id) || !Number.isFinite(lat) || !Number.isFinite(lon)) continue;
|
||||
nodes.set(id, { id, lat, lon, tags: parseTags(match[2] || "") });
|
||||
}
|
||||
for (const match of xmlText.matchAll(/<way\b([^>]*)>([\s\S]*?)<\/way>/g)) {
|
||||
const attrs = parseAttrs(match[1]);
|
||||
const id = Number(attrs.id);
|
||||
if (!Number.isFinite(id)) continue;
|
||||
const body = match[2] || "";
|
||||
const refs = [...body.matchAll(/<nd\b([^>]*)\/>/g)]
|
||||
.map((refMatch) => Number(parseAttrs(refMatch[1]).ref))
|
||||
.filter(Number.isFinite);
|
||||
ways.set(id, { id, refs, tags: parseTags(body) });
|
||||
}
|
||||
return { nodes, ways };
|
||||
}
|
||||
|
||||
function parseAttrs(text) {
|
||||
const attrs = {};
|
||||
for (const match of text.matchAll(/([\w:.-]+)=(["'])(.*?)\2/g)) {
|
||||
attrs[match[1]] = decodeXml(match[3]);
|
||||
}
|
||||
return attrs;
|
||||
}
|
||||
|
||||
function parseTags(text) {
|
||||
const tags = {};
|
||||
for (const match of text.matchAll(/<tag\b([^>]*)\/>/g)) {
|
||||
const attrs = parseAttrs(match[1]);
|
||||
if (attrs.k !== undefined) tags[attrs.k] = attrs.v ?? "";
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
function decodeXml(value) {
|
||||
return value
|
||||
.replaceAll(""", "\"")
|
||||
.replaceAll("'", "'")
|
||||
.replaceAll("<", "<")
|
||||
.replaceAll(">", ">")
|
||||
.replaceAll("&", "&");
|
||||
}
|
||||
|
||||
function makeClipPolygon(bbox, pad) {
|
||||
const b = expandBounds(bbox, pad);
|
||||
return {
|
||||
@@ -301,19 +357,33 @@ function emptyCollection() {
|
||||
return { type: "FeatureCollection", features: [] };
|
||||
}
|
||||
|
||||
function splitLayers(dir, arrowScaleValue) {
|
||||
function splitLayers(dir, arrowScaleValue, osm) {
|
||||
const plain = JSON.parse(fs.readFileSync(path.join(dir, "plain.geojson"), "utf8"));
|
||||
const lanePolygons = JSON.parse(fs.readFileSync(path.join(dir, "lane_polygons.geojson"), "utf8"));
|
||||
const markings = JSON.parse(fs.readFileSync(path.join(dir, "lane_markings.geojson"), "utf8"));
|
||||
const intersections = JSON.parse(fs.readFileSync(path.join(dir, "intersection_markings.geojson"), "utf8"));
|
||||
const crosswalkData = buildCrosswalks(osm);
|
||||
const serviceWayIds = new Set([...osm.ways.values()]
|
||||
.filter((way) => way.tags.highway === "service")
|
||||
.map((way) => way.id));
|
||||
const out = {
|
||||
roadSurface: emptyCollection(),
|
||||
intersectionSurface: emptyCollection(),
|
||||
sidewalks: emptyCollection(),
|
||||
laneSeparators: emptyCollection(),
|
||||
centerLines: emptyCollection(),
|
||||
vehicleStopLines: emptyCollection(),
|
||||
vehicleStopLines: crosswalkData.stopLines,
|
||||
laneArrows: emptyCollection(),
|
||||
sidewalkCorners: intersections,
|
||||
crosswalks: crosswalkData.stripes,
|
||||
};
|
||||
const serviceDrivingPolygons = [];
|
||||
|
||||
for (const feature of plain.features || []) {
|
||||
if (feature.properties?.type === "intersection") {
|
||||
out.intersectionSurface.features.push(feature);
|
||||
}
|
||||
}
|
||||
|
||||
for (const feature of lanePolygons.features || []) {
|
||||
const type = feature.properties?.type;
|
||||
@@ -321,19 +391,315 @@ function splitLayers(dir, arrowScaleValue) {
|
||||
out.sidewalks.features.push(feature);
|
||||
} else {
|
||||
out.roadSurface.features.push(feature);
|
||||
if (type === "Driving" && hasAnyWayId(feature.properties?.osm_way_ids, serviceWayIds)) {
|
||||
serviceDrivingPolygons.push({
|
||||
bbox: featureBounds(feature),
|
||||
rings: polygonRings(feature.geometry),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const feature of markings.features || []) {
|
||||
const type = feature.properties?.type;
|
||||
if (type === "lane separator") out.laneSeparators.features.push(feature);
|
||||
if (type === "center line") out.centerLines.features.push(feature);
|
||||
if (type === "vehicle stop line") out.vehicleStopLines.features.push(feature);
|
||||
if (type === "lane arrow") out.laneArrows.features.push(scaleFeature(feature, arrowScaleValue));
|
||||
const conflictsWithCrosswalk = isInAnyPolygon(feature, crosswalkData.zones, "intersects");
|
||||
if (type === "lane separator" && !conflictsWithCrosswalk) out.laneSeparators.features.push(feature);
|
||||
if (type === "center line" && !conflictsWithCrosswalk && !isInAnyPolygon(feature, serviceDrivingPolygons)) out.centerLines.features.push(feature);
|
||||
if (type === "vehicle stop line" && !isInAnyPolygon(feature, crosswalkData.stopLineExclusionZones, "intersects")) {
|
||||
out.vehicleStopLines.features.push(feature);
|
||||
}
|
||||
if (type === "lane arrow" && !conflictsWithCrosswalk && !isInAnyPolygon(feature, serviceDrivingPolygons)) out.laneArrows.features.push(scaleFeature(feature, arrowScaleValue));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function hasAnyWayId(value, ids) {
|
||||
const values = Array.isArray(value) ? value : [value];
|
||||
return values.some((id) => ids.has(Number(id)));
|
||||
}
|
||||
|
||||
function polygonRings(geometry) {
|
||||
if (!geometry?.coordinates) return [];
|
||||
if (geometry.type === "Polygon") return [geometry.coordinates];
|
||||
if (geometry.type === "MultiPolygon") return geometry.coordinates;
|
||||
return [];
|
||||
}
|
||||
|
||||
function isInAnyPolygon(feature, polygons, mode = "point") {
|
||||
if (!polygons.length) return false;
|
||||
const featureBbox = featureBounds(feature);
|
||||
const points = mode === "intersects" ? featurePoints(feature) : [representativePoint(feature)].filter(Boolean);
|
||||
if (!points.length) return false;
|
||||
return polygons.some(({ bbox, rings }) => (
|
||||
bboxesOverlap(featureBbox, bbox, 0.00001) &&
|
||||
points.some((point) => (
|
||||
point[0] >= bbox.minLon - 0.00001 &&
|
||||
point[0] <= bbox.maxLon + 0.00001 &&
|
||||
point[1] >= bbox.minLat - 0.00001 &&
|
||||
point[1] <= bbox.maxLat + 0.00001 &&
|
||||
rings.some((polygon) => pointInPolygon(point, polygon))
|
||||
))
|
||||
));
|
||||
}
|
||||
|
||||
function featurePoints(feature) {
|
||||
const coords = [];
|
||||
collectCoords(feature.geometry?.coordinates, coords);
|
||||
if (!coords.length) return [];
|
||||
return [
|
||||
coords[0],
|
||||
coords[Math.floor(coords.length / 2)],
|
||||
coords[coords.length - 1],
|
||||
...bboxCorners(featureBounds(feature)),
|
||||
];
|
||||
}
|
||||
|
||||
function bboxCorners(bbox) {
|
||||
return [
|
||||
[bbox.minLon, bbox.minLat],
|
||||
[bbox.maxLon, bbox.minLat],
|
||||
[bbox.maxLon, bbox.maxLat],
|
||||
[bbox.minLon, bbox.maxLat],
|
||||
];
|
||||
}
|
||||
|
||||
function bboxesOverlap(a, b, pad = 0) {
|
||||
return a.minLon <= b.maxLon + pad &&
|
||||
a.maxLon >= b.minLon - pad &&
|
||||
a.minLat <= b.maxLat + pad &&
|
||||
a.maxLat >= b.minLat - pad;
|
||||
}
|
||||
|
||||
function representativePoint(feature) {
|
||||
const coords = [];
|
||||
collectCoords(feature.geometry?.coordinates, coords);
|
||||
if (!coords.length) return null;
|
||||
return coords[Math.floor(coords.length / 2)];
|
||||
}
|
||||
|
||||
function pointInPolygon(point, rings) {
|
||||
if (!rings?.length || !pointInRing(point, rings[0])) return false;
|
||||
return !rings.slice(1).some((ring) => pointInRing(point, ring));
|
||||
}
|
||||
|
||||
function pointInRing([x, y], ring) {
|
||||
let inside = false;
|
||||
for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
|
||||
const xi = ring[i][0];
|
||||
const yi = ring[i][1];
|
||||
const xj = ring[j][0];
|
||||
const yj = ring[j][1];
|
||||
const intersect = ((yi > y) !== (yj > y)) &&
|
||||
x < ((xj - xi) * (y - yi)) / (yj - yi) + xi;
|
||||
if (intersect) inside = !inside;
|
||||
}
|
||||
return inside;
|
||||
}
|
||||
|
||||
function buildCrosswalks(osm) {
|
||||
const crossingNodes = [...osm.nodes.values()].filter((node) => {
|
||||
if (node.tags.highway !== "crossing") return false;
|
||||
const markings = node.tags["crossing:markings"];
|
||||
return !(markings && ["no", "none", "unmarked"].includes(markings));
|
||||
});
|
||||
const clusterCenters = crossingClusters(crossingNodes);
|
||||
const stripes = emptyCollection();
|
||||
const stopLines = emptyCollection();
|
||||
const zones = [];
|
||||
const stopLineExclusionZones = [];
|
||||
for (const node of crossingNodes) {
|
||||
const way = findCrossingWay(osm, node.id);
|
||||
const vector = crossingVector(osm, way, node.id);
|
||||
if (!vector) continue;
|
||||
const crosswalk = crosswalkGeometry(node, way, vector, clusterCenters.get(node.id));
|
||||
if (!crosswalk) continue;
|
||||
stripes.features.push(...crosswalk.stripes);
|
||||
if (crosswalk.stopLine) stopLines.features.push(crosswalk.stopLine);
|
||||
zones.push({
|
||||
bbox: featureBounds(crosswalk.zone),
|
||||
rings: polygonRings(crosswalk.zone.geometry),
|
||||
});
|
||||
stopLineExclusionZones.push({
|
||||
bbox: featureBounds(crosswalk.stopLineExclusionZone),
|
||||
rings: polygonRings(crosswalk.stopLineExclusionZone.geometry),
|
||||
});
|
||||
}
|
||||
return { stripes, stopLines, zones, stopLineExclusionZones };
|
||||
}
|
||||
|
||||
function crossingClusters(nodes) {
|
||||
const clusters = [];
|
||||
for (const node of nodes) {
|
||||
let cluster = clusters.find((candidate) => {
|
||||
const center = clusterCenter(candidate);
|
||||
return distanceMeters(node, center) <= 45;
|
||||
});
|
||||
if (!cluster) {
|
||||
cluster = [];
|
||||
clusters.push(cluster);
|
||||
}
|
||||
cluster.push(node);
|
||||
}
|
||||
const centers = new Map();
|
||||
for (const cluster of clusters) {
|
||||
if (cluster.length < 2) continue;
|
||||
const center = clusterCenter(cluster);
|
||||
for (const node of cluster) centers.set(node.id, center);
|
||||
}
|
||||
return centers;
|
||||
}
|
||||
|
||||
function clusterCenter(nodes) {
|
||||
return {
|
||||
lon: nodes.reduce((sum, node) => sum + node.lon, 0) / nodes.length,
|
||||
lat: nodes.reduce((sum, node) => sum + node.lat, 0) / nodes.length,
|
||||
};
|
||||
}
|
||||
|
||||
function distanceMeters(a, b) {
|
||||
const meters = metersForLat((a.lat + b.lat) / 2);
|
||||
return Math.hypot((a.lon - b.lon) * meters.lon, (a.lat - b.lat) * meters.lat);
|
||||
}
|
||||
|
||||
function findCrossingWay(osm, nodeId) {
|
||||
const candidates = [...osm.ways.values()]
|
||||
.filter((way) => way.tags.highway && way.refs.includes(nodeId));
|
||||
return candidates.find((way) => way.tags.highway !== "service") || candidates[0] || null;
|
||||
}
|
||||
|
||||
function crossingVector(osm, way, nodeId) {
|
||||
if (!way) return null;
|
||||
const index = way.refs.indexOf(nodeId);
|
||||
const prev = osm.nodes.get(way.refs[index - 1]);
|
||||
const next = osm.nodes.get(way.refs[index + 1]);
|
||||
const current = osm.nodes.get(nodeId);
|
||||
if (prev && next) return [next.lon - prev.lon, next.lat - prev.lat];
|
||||
if (prev && current) return [current.lon - prev.lon, current.lat - prev.lat];
|
||||
if (next && current) return [next.lon - current.lon, next.lat - current.lat];
|
||||
return null;
|
||||
}
|
||||
|
||||
function crosswalkGeometry(node, way, vector, intersectionCenter) {
|
||||
const meters = metersForLat(node.lat);
|
||||
const stripeLength = crosswalkLengthMeters(way);
|
||||
const stripeWidth = 0.45;
|
||||
const gap = 0.45;
|
||||
const count = 6;
|
||||
const total = count * stripeWidth + (count - 1) * gap;
|
||||
const roadUnit = normalizeMetersVector(vector, meters);
|
||||
if (!roadUnit) return null;
|
||||
const acrossUnit = [-roadUnit[1], roadUnit[0]];
|
||||
const center = [node.lon, node.lat];
|
||||
const zoneCoords = rectangleMeters(center, roadUnit, acrossUnit, stripeLength + 0.8, total + 0.8, meters);
|
||||
const stopLineExclusionCoords = rectangleMeters(center, roadUnit, acrossUnit, stripeLength + 5, total + 1.5, meters);
|
||||
const zone = {
|
||||
type: "Feature",
|
||||
properties: {
|
||||
type: "crosswalk zone",
|
||||
crossing_node_id: node.id,
|
||||
},
|
||||
geometry: { type: "Polygon", coordinates: [zoneCoords] },
|
||||
};
|
||||
const stopLineExclusionZone = {
|
||||
type: "Feature",
|
||||
properties: {
|
||||
type: "crosswalk stop line exclusion zone",
|
||||
crossing_node_id: node.id,
|
||||
},
|
||||
geometry: { type: "Polygon", coordinates: [stopLineExclusionCoords] },
|
||||
};
|
||||
const stripes = [];
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
const offset = -total / 2 + stripeWidth / 2 + i * (stripeWidth + gap);
|
||||
const stripeCenter = addMeters(center, acrossUnit, offset, meters);
|
||||
const coords = rectangleMeters(stripeCenter, roadUnit, acrossUnit, stripeLength, stripeWidth, meters);
|
||||
stripes.push({
|
||||
type: "Feature",
|
||||
properties: {
|
||||
type: "crosswalk stripe",
|
||||
crossing_node_id: node.id,
|
||||
highway: way?.tags.highway || null,
|
||||
},
|
||||
geometry: { type: "Polygon", coordinates: [coords] },
|
||||
});
|
||||
}
|
||||
return {
|
||||
stripes,
|
||||
zone,
|
||||
stopLineExclusionZone,
|
||||
stopLine: syntheticStopLine(node, way, roadUnit, acrossUnit, stripeLength, total, intersectionCenter, meters),
|
||||
};
|
||||
}
|
||||
|
||||
function syntheticStopLine(node, way, roadUnit, acrossUnit, stripeLength, crosswalkWidth, intersectionCenter, meters) {
|
||||
if (!intersectionCenter) return null;
|
||||
const offset = stripeLength / 2 + 1.2;
|
||||
const candidateA = addMeters([node.lon, node.lat], roadUnit, offset, meters);
|
||||
const candidateB = addMeters([node.lon, node.lat], roadUnit, -offset, meters);
|
||||
const stopCenter = pointDistanceMeters(candidateA, intersectionCenter, meters) >= pointDistanceMeters(candidateB, intersectionCenter, meters)
|
||||
? candidateA
|
||||
: candidateB;
|
||||
const coords = rectangleMeters(stopCenter, acrossUnit, roadUnit, crosswalkWidth + 0.8, 0.45, meters);
|
||||
return {
|
||||
type: "Feature",
|
||||
properties: {
|
||||
type: "vehicle stop line",
|
||||
source: "crosswalk",
|
||||
crossing_node_id: node.id,
|
||||
highway: way?.tags.highway || null,
|
||||
},
|
||||
geometry: { type: "Polygon", coordinates: [coords] },
|
||||
};
|
||||
}
|
||||
|
||||
function pointDistanceMeters([lon, lat], point, meters) {
|
||||
return Math.hypot((lon - point.lon) * meters.lon, (lat - point.lat) * meters.lat);
|
||||
}
|
||||
|
||||
function crosswalkLengthMeters(way) {
|
||||
const lanes = Number(way?.tags.lanes);
|
||||
if (Number.isFinite(lanes) && lanes > 0) return Math.max(5, lanes * 3.2 + 1.2);
|
||||
if (way?.tags.highway === "secondary") return 8.5;
|
||||
if (way?.tags.highway === "service") return 4.5;
|
||||
return 7.5;
|
||||
}
|
||||
|
||||
function metersForLat(lat) {
|
||||
return {
|
||||
lon: 111320 * Math.cos((lat * Math.PI) / 180),
|
||||
lat: 110540,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeMetersVector([dxLon, dyLat], meters) {
|
||||
const x = dxLon * meters.lon;
|
||||
const y = dyLat * meters.lat;
|
||||
const length = Math.hypot(x, y);
|
||||
if (!Number.isFinite(length) || length === 0) return null;
|
||||
return [x / length, y / length];
|
||||
}
|
||||
|
||||
function addMeters([lon, lat], [ux, uy], distance, meters) {
|
||||
return [lon + (ux * distance) / meters.lon, lat + (uy * distance) / meters.lat];
|
||||
}
|
||||
|
||||
function rectangleMeters(center, axisUnit, acrossUnit, axisWidth, acrossLength, meters) {
|
||||
const halfAxis = axisWidth / 2;
|
||||
const halfAcross = acrossLength / 2;
|
||||
const corners = [
|
||||
[-halfAxis, -halfAcross],
|
||||
[halfAxis, -halfAcross],
|
||||
[halfAxis, halfAcross],
|
||||
[-halfAxis, halfAcross],
|
||||
[-halfAxis, -halfAcross],
|
||||
];
|
||||
return corners.map(([axis, across]) => {
|
||||
const x = axisUnit[0] * axis + acrossUnit[0] * across;
|
||||
const y = axisUnit[1] * axis + acrossUnit[1] * across;
|
||||
return [center[0] + x / meters.lon, center[1] + y / meters.lat];
|
||||
});
|
||||
}
|
||||
|
||||
function scaleFeature(feature, scale) {
|
||||
const coords = [];
|
||||
collectCoords(feature.geometry.coordinates, coords);
|
||||
@@ -429,14 +795,16 @@ project.setPresetHomePath(str(Path(PROJECT_PATH).parent))
|
||||
|
||||
layers = {
|
||||
"road_surface": make_layer("road_surface", "osm2streets road surface", "43,43,40,255", "30,30,28,255", "0.04"),
|
||||
"intersection_surface": make_layer("intersection_surface", "osm2streets intersection surface", "43,43,40,255", "30,30,28,255", "0.04"),
|
||||
"sidewalks": make_layer("sidewalks", "osm2streets sidewalks", "190,190,182,255", "156,156,148,255", "0.025"),
|
||||
"sidewalk_corners": make_layer("sidewalk_corners", "osm2streets sidewalk corners", "190,190,182,255", "156,156,148,255", "0.025"),
|
||||
"crosswalks": make_layer("crosswalks", "osm2streets crosswalks", "255,255,246,255"),
|
||||
"lane_separators": make_layer("lane_separators", "osm2streets lane separators", "238,238,230,255"),
|
||||
"center_lines": make_layer("center_lines", "osm2streets center lines", "245,190,42,255"),
|
||||
"vehicle_stop_lines": make_layer("vehicle_stop_lines", "osm2streets vehicle stop lines", "255,255,246,255"),
|
||||
"lane_arrows": make_layer("lane_arrows_webscale", "osm2streets lane arrows", "255,255,246,255", "43,43,40,200", "0.015"),
|
||||
}
|
||||
draw_order = ["road_surface", "sidewalks", "sidewalk_corners", "lane_separators", "center_lines", "vehicle_stop_lines", "lane_arrows"]
|
||||
draw_order = ["road_surface", "intersection_surface", "sidewalks", "sidewalk_corners", "lane_separators", "center_lines", "crosswalks", "vehicle_stop_lines", "lane_arrows"]
|
||||
for key in draw_order:
|
||||
project.addMapLayer(layers[key], False)
|
||||
root = project.layerTreeRoot()
|
||||
|
||||
Reference in New Issue
Block a user