feat: osm2streets上游箭头修复
This commit is contained in:
@@ -14,12 +14,16 @@ const qgisApp = config.qgisApp;
|
||||
const qgisMacOS = path.join(qgisApp, "Contents", "MacOS");
|
||||
const qgisPython = path.join(qgisMacOS, "python3.12");
|
||||
const ogr2ogr = path.join(qgisMacOS, "ogr2ogr");
|
||||
const normalizeLaneArrowsScript = path.join(repoRoot, "scripts", "normalize-lane-arrows.py");
|
||||
const inputPath = path.resolve(config.input);
|
||||
const outDir = path.resolve(config.outDir);
|
||||
const gpkgPath = path.resolve(config.gpkg);
|
||||
const projectPath = path.resolve(config.project);
|
||||
const previewPath = path.resolve(config.preview);
|
||||
const arrowScale = Number(config.arrowScale);
|
||||
const arrowMergeTriangles = config.arrowMergeTriangles !== false;
|
||||
const arrowOutlineSimplifyMeters = Number(config.arrowOutlineSimplifyMeters ?? 0.05);
|
||||
const intersectionCornerSourceMaxDimensionMeters = Number(config.intersectionCornerSourceMaxDimensionMeters ?? 2.6);
|
||||
const clipPad = Number(config.clipPad);
|
||||
const canvasPad = Number(config.canvasPad);
|
||||
const previewPad = Number(config.previewPad);
|
||||
@@ -28,6 +32,12 @@ const layerPrefix = config.layerPrefix || "osm2streets";
|
||||
if (!Number.isFinite(arrowScale) || arrowScale <= 0) {
|
||||
throw new Error(`Invalid arrowScale: ${config.arrowScale}`);
|
||||
}
|
||||
if (!Number.isFinite(arrowOutlineSimplifyMeters) || arrowOutlineSimplifyMeters < 0) {
|
||||
throw new Error(`Invalid arrowOutlineSimplifyMeters: ${config.arrowOutlineSimplifyMeters}`);
|
||||
}
|
||||
if (!Number.isFinite(intersectionCornerSourceMaxDimensionMeters) || intersectionCornerSourceMaxDimensionMeters <= 0) {
|
||||
throw new Error(`Invalid intersectionCornerSourceMaxDimensionMeters: ${config.intersectionCornerSourceMaxDimensionMeters}`);
|
||||
}
|
||||
for (const [key, value] of [["clipPad", clipPad], ["canvasPad", canvasPad], ["previewPad", previewPad]]) {
|
||||
if (!Number.isFinite(value) || value < 0) {
|
||||
throw new Error(`Invalid ${key}: ${config[key]}`);
|
||||
@@ -41,6 +51,9 @@ for (const exe of [ogr2ogr, qgisPython]) {
|
||||
throw new Error(`QGIS executable not found: ${exe}`);
|
||||
}
|
||||
}
|
||||
if (!fs.existsSync(normalizeLaneArrowsScript)) {
|
||||
throw new Error(`Lane-arrow normalizer not found: ${normalizeLaneArrowsScript}`);
|
||||
}
|
||||
|
||||
fs.mkdirSync(outDir, { recursive: true });
|
||||
fs.mkdirSync(path.dirname(gpkgPath), { recursive: true });
|
||||
@@ -60,7 +73,12 @@ 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, osm);
|
||||
const split = splitLayers(
|
||||
outDir,
|
||||
arrowScale,
|
||||
intersectionCornerSourceMaxDimensionMeters,
|
||||
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);
|
||||
@@ -68,6 +86,10 @@ 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);
|
||||
if (arrowMergeTriangles) {
|
||||
normalizeLaneArrows(path.join(outDir, "lane_arrows_webscale.geojson"), arrowOutlineSimplifyMeters);
|
||||
split.laneArrows = JSON.parse(fs.readFileSync(path.join(outDir, "lane_arrows_webscale.geojson"), "utf8"));
|
||||
}
|
||||
writeJson(path.join(outDir, "sidewalk_corners.geojson"), split.sidewalkCorners);
|
||||
writeJson(path.join(outDir, "crosswalks.geojson"), split.crosswalks);
|
||||
writeJson(path.join(outDir, "osm2streets_scene.geojson"), mergedScene(split));
|
||||
@@ -161,6 +183,9 @@ function loadConfig(file, cliArgs) {
|
||||
project: "project",
|
||||
preview: "preview",
|
||||
arrowScale: "arrowScale",
|
||||
arrowMergeTriangles: "arrowMergeTriangles",
|
||||
arrowOutlineSimplifyMeters: "arrowOutlineSimplifyMeters",
|
||||
intersectionCornerSourceMaxDimensionMeters: "intersectionCornerSourceMaxDimensionMeters",
|
||||
clipPad: "clipPad",
|
||||
pad: "clipPad",
|
||||
canvasPad: "canvasPad",
|
||||
@@ -428,7 +453,7 @@ function sceneStyle() {
|
||||
};
|
||||
}
|
||||
|
||||
function splitLayers(dir, arrowScaleValue, osm) {
|
||||
function splitLayers(dir, arrowScaleValue, maxCornerDimensionMeters, 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"));
|
||||
@@ -445,7 +470,7 @@ function splitLayers(dir, arrowScaleValue, osm) {
|
||||
centerLines: emptyCollection(),
|
||||
vehicleStopLines: crosswalkData.stopLines,
|
||||
laneArrows: emptyCollection(),
|
||||
sidewalkCorners: intersections,
|
||||
sidewalkCorners: filteredSidewalkCorners(intersections, maxCornerDimensionMeters),
|
||||
crosswalks: crosswalkData.stripes,
|
||||
};
|
||||
const serviceDrivingPolygons = [];
|
||||
@@ -455,7 +480,6 @@ function splitLayers(dir, arrowScaleValue, osm) {
|
||||
out.intersectionSurface.features.push(feature);
|
||||
}
|
||||
}
|
||||
|
||||
for (const feature of lanePolygons.features || []) {
|
||||
const type = feature.properties?.type;
|
||||
if (type === "Sidewalk" || type === "Footway") {
|
||||
@@ -479,7 +503,9 @@ function splitLayers(dir, arrowScaleValue, osm) {
|
||||
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));
|
||||
if (type === "lane arrow" && !conflictsWithCrosswalk && !isInAnyPolygon(feature, serviceDrivingPolygons)) {
|
||||
out.laneArrows.features.push(scaleFeature(feature, arrowScaleValue));
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
@@ -490,6 +516,30 @@ function hasAnyWayId(value, ids) {
|
||||
return values.some((id) => ids.has(Number(id)));
|
||||
}
|
||||
|
||||
function filteredSidewalkCorners(intersections, maxDimensionMeters) {
|
||||
const out = emptyCollection();
|
||||
for (const feature of intersections.features || []) {
|
||||
if (feature.properties?.type !== "sidewalk corner") continue;
|
||||
const dimension = maxFeatureDimensionMeters(feature);
|
||||
if (dimension === null || dimension > maxDimensionMeters) continue;
|
||||
out.features.push(feature);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function maxFeatureDimensionMeters(feature) {
|
||||
const points = [];
|
||||
collectCoords(feature.geometry?.coordinates, points);
|
||||
if (points.length === 0) return null;
|
||||
const lat = points.reduce((sum, point) => sum + point[1], 0) / points.length;
|
||||
const meters = metersForLat(lat);
|
||||
const xs = points.map((point) => point[0]);
|
||||
const ys = points.map((point) => point[1]);
|
||||
const width = (Math.max(...xs) - Math.min(...xs)) * meters.lon;
|
||||
const height = (Math.max(...ys) - Math.min(...ys)) * meters.lat;
|
||||
return Math.max(width, height);
|
||||
}
|
||||
|
||||
function polygonRings(geometry) {
|
||||
if (!geometry?.coordinates) return [];
|
||||
if (geometry.type === "Polygon") return [geometry.coordinates];
|
||||
@@ -846,6 +896,26 @@ function scaleCoords(obj, cx, cy, scale) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
function normalizeLaneArrows(geojsonPath, outlineSimplifyMeters) {
|
||||
execFileSync(qgisPython, [
|
||||
normalizeLaneArrowsScript,
|
||||
"--input", geojsonPath,
|
||||
"--outline-simplify-meters", String(outlineSimplifyMeters),
|
||||
], {
|
||||
stdio: "inherit",
|
||||
env: {
|
||||
...process.env,
|
||||
...qgisEnv(),
|
||||
QT_QPA_PLATFORM: "offscreen",
|
||||
PYTHONHOME: path.join(qgisApp, "Contents", "Frameworks"),
|
||||
PYTHONPATH: [
|
||||
path.join(qgisApp, "Contents", "Resources", "python"),
|
||||
path.join(qgisApp, "Contents", "Resources", "python", "plugins"),
|
||||
].join(path.delimiter),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function importLayer(gpkg, source, layerName, update, env) {
|
||||
const args = ["-f", "GPKG"];
|
||||
if (update) args.push("-update", "-overwrite");
|
||||
|
||||
Reference in New Issue
Block a user