forked from MapComplete/MapComplete
More or less working version of advanced conflation
This commit is contained in:
parent
7f99e76b0c
commit
3176a4d665
11 changed files with 634 additions and 524 deletions
|
@ -25,7 +25,7 @@ export class GeoOperations {
|
|||
}
|
||||
|
||||
static centerpointCoordinates(feature: any): [number, number] {
|
||||
return <[number, number]> turf.center(feature).geometry.coordinates;
|
||||
return <[number, number]>turf.center(feature).geometry.coordinates;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -37,10 +37,10 @@ export class GeoOperations {
|
|||
return turf.distance(lonlat0, lonlat1, {units: "meters"})
|
||||
}
|
||||
|
||||
static convexHull(featureCollection, options: {concavity?: number}){
|
||||
static convexHull(featureCollection, options: { concavity?: number }) {
|
||||
return turf.convex(featureCollection, options)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Calculates the overlap of 'feature' with every other specified feature.
|
||||
* The features with which 'feature' overlaps, are returned together with their overlap area in m²
|
||||
|
@ -199,8 +199,8 @@ export class GeoOperations {
|
|||
|
||||
static buffer(feature: any, bufferSizeInMeter: number) {
|
||||
return turf.buffer(feature, bufferSizeInMeter / 1000, {
|
||||
units:'kilometers'
|
||||
} )
|
||||
units: 'kilometers'
|
||||
})
|
||||
}
|
||||
|
||||
static bbox(feature: any) {
|
||||
|
@ -350,263 +350,166 @@ export class GeoOperations {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the intersection between two features.
|
||||
* Returns the length if intersecting a linestring and a (multi)polygon (in meters), returns a surface area (in m²) if intersecting two (multi)polygons
|
||||
* Returns 0 if both are linestrings
|
||||
* Returns null if the features are not intersecting
|
||||
*/
|
||||
private static calculateInstersection(feature, otherFeature, featureBBox: BBox, otherFeatureBBox?: BBox): number {
|
||||
if (feature.geometry.type === "LineString") {
|
||||
|
||||
|
||||
otherFeatureBBox = otherFeatureBBox ?? BBox.get(otherFeature);
|
||||
const overlaps = featureBBox.overlapsWith(otherFeatureBBox)
|
||||
if (!overlaps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Calculate the length of the intersection
|
||||
|
||||
|
||||
let intersectionPoints = turf.lineIntersect(feature, otherFeature);
|
||||
if (intersectionPoints.features.length == 0) {
|
||||
// No intersections.
|
||||
// If one point is inside of the polygon, all points are
|
||||
|
||||
|
||||
const coors = feature.geometry.coordinates;
|
||||
const startCoor = coors[0]
|
||||
if (this.inside(startCoor, otherFeature)) {
|
||||
return this.lengthInMeters(feature)
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
let intersectionPointsArray = intersectionPoints.features.map(d => {
|
||||
return d.geometry.coordinates
|
||||
});
|
||||
|
||||
if (otherFeature.geometry.type === "LineString") {
|
||||
if (intersectionPointsArray.length > 0) {
|
||||
return 0
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (intersectionPointsArray.length == 1) {
|
||||
// We need to add the start- or endpoint of the current feature, depending on which one is embedded
|
||||
const coors = feature.geometry.coordinates;
|
||||
const startCoor = coors[0]
|
||||
if (this.inside(startCoor, otherFeature)) {
|
||||
// The startpoint is embedded
|
||||
intersectionPointsArray.push(startCoor)
|
||||
} else {
|
||||
intersectionPointsArray.push(coors[coors.length - 1])
|
||||
}
|
||||
}
|
||||
|
||||
let intersection = turf.lineSlice(turf.point(intersectionPointsArray[0]), turf.point(intersectionPointsArray[1]), feature);
|
||||
|
||||
if (intersection == null) {
|
||||
return null;
|
||||
}
|
||||
const intersectionSize = turf.length(intersection); // in km
|
||||
return intersectionSize * 1000
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (feature.geometry.type === "Polygon" || feature.geometry.type === "MultiPolygon") {
|
||||
const otherFeatureBBox = BBox.get(otherFeature);
|
||||
const overlaps = featureBBox.overlapsWith(otherFeatureBBox)
|
||||
if (!overlaps) {
|
||||
return null;
|
||||
}
|
||||
if (otherFeature.geometry.type === "LineString") {
|
||||
return this.calculateInstersection(otherFeature, feature, otherFeatureBBox, featureBBox)
|
||||
}
|
||||
|
||||
try{
|
||||
|
||||
const intersection = turf.intersect(feature, otherFeature);
|
||||
if (intersection == null) {
|
||||
return null;
|
||||
}
|
||||
return turf.area(intersection); // in m²
|
||||
}catch(e){
|
||||
if(e.message === "Each LinearRing of a Polygon must have 4 or more Positions."){
|
||||
// WORKAROUND TIME!
|
||||
// See https://github.com/Turfjs/turf/pull/2238
|
||||
return null;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
}
|
||||
throw "CalculateIntersection fallthrough: can not calculate an intersection between features"
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates line intersection between two features.
|
||||
*/
|
||||
public static LineIntersections(feature, otherFeature): [number,number][]{
|
||||
return turf.lineIntersect(feature, otherFeature).features.map(p =><[number,number]> p.geometry.coordinates)
|
||||
public static LineIntersections(feature, otherFeature): [number, number][] {
|
||||
return turf.lineIntersect(feature, otherFeature).features.map(p => <[number, number]>p.geometry.coordinates)
|
||||
}
|
||||
|
||||
public static AsGpx(feature, generatedWithLayer?: LayerConfig){
|
||||
|
||||
|
||||
public static AsGpx(feature, generatedWithLayer?: LayerConfig) {
|
||||
|
||||
const metadata = {}
|
||||
const tags = feature.properties
|
||||
|
||||
if(generatedWithLayer !== undefined){
|
||||
|
||||
|
||||
if (generatedWithLayer !== undefined) {
|
||||
|
||||
metadata["name"] = generatedWithLayer.title?.GetRenderValue(tags)?.Subs(tags)?.txt
|
||||
metadata["desc"] = "Generated with MapComplete layer "+generatedWithLayer.id
|
||||
if(tags._backend?.contains("openstreetmap")){
|
||||
metadata["copyright"]= "Data copyrighted by OpenStreetMap-contributors, freely available under ODbL. See https://www.openstreetmap.org/copyright"
|
||||
metadata["desc"] = "Generated with MapComplete layer " + generatedWithLayer.id
|
||||
if (tags._backend?.contains("openstreetmap")) {
|
||||
metadata["copyright"] = "Data copyrighted by OpenStreetMap-contributors, freely available under ODbL. See https://www.openstreetmap.org/copyright"
|
||||
metadata["author"] = tags["_last_edit:contributor"]
|
||||
metadata["link"]= "https://www.openstreetmap.org/"+tags.id
|
||||
metadata["link"] = "https://www.openstreetmap.org/" + tags.id
|
||||
metadata["time"] = tags["_last_edit:timestamp"]
|
||||
}else{
|
||||
} else {
|
||||
metadata["time"] = new Date().toISOString()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return togpx(feature, {
|
||||
creator: "MapComplete "+Constants.vNumber,
|
||||
creator: "MapComplete " + Constants.vNumber,
|
||||
metadata
|
||||
})
|
||||
}
|
||||
|
||||
public static IdentifieCommonSegments(coordinatess: [number,number][][] ): {
|
||||
|
||||
public static IdentifieCommonSegments(coordinatess: [number, number][][]): {
|
||||
originalIndex: number,
|
||||
segmentShardWith: number[],
|
||||
coordinates: []
|
||||
}[]{
|
||||
|
||||
}[] {
|
||||
|
||||
// An edge. Note that the edge might be reversed to fix the sorting condition: start[0] < end[0] && (start[0] != end[0] || start[0] < end[1])
|
||||
type edge = {start: [number, number], end: [number, number], intermediate: [number,number][], members: {index:number, isReversed: boolean}[]}
|
||||
type edge = { start: [number, number], end: [number, number], intermediate: [number, number][], members: { index: number, isReversed: boolean }[] }
|
||||
|
||||
// The strategy:
|
||||
// 1. Index _all_ edges from _every_ linestring. Index them by starting key, gather which relations run over them
|
||||
// 2. Join these edges back together - as long as their membership groups are the same
|
||||
// 3. Convert to results
|
||||
|
||||
|
||||
const allEdgesByKey = new Map<string, edge>()
|
||||
|
||||
for (let index = 0; index < coordinatess.length; index++){
|
||||
for (let index = 0; index < coordinatess.length; index++) {
|
||||
const coordinates = coordinatess[index];
|
||||
for (let i = 0; i < coordinates.length - 1; i++){
|
||||
|
||||
for (let i = 0; i < coordinates.length - 1; i++) {
|
||||
|
||||
const c0 = coordinates[i];
|
||||
const c1 = coordinates[i + 1]
|
||||
const isReversed = (c0[0] > c1[0]) || (c0[0] == c1[0] && c0[1] > c1[1])
|
||||
|
||||
let key : string
|
||||
if(isReversed){
|
||||
key = ""+c1+";"+c0
|
||||
}else{
|
||||
key = ""+c0+";"+c1
|
||||
|
||||
let key: string
|
||||
if (isReversed) {
|
||||
key = "" + c1 + ";" + c0
|
||||
} else {
|
||||
key = "" + c0 + ";" + c1
|
||||
}
|
||||
const member = {index, isReversed}
|
||||
if(allEdgesByKey.has(key)){
|
||||
if (allEdgesByKey.has(key)) {
|
||||
allEdgesByKey.get(key).members.push(member)
|
||||
continue
|
||||
}
|
||||
|
||||
let edge : edge;
|
||||
if(!isReversed){
|
||||
|
||||
let edge: edge;
|
||||
if (!isReversed) {
|
||||
edge = {
|
||||
start : c0,
|
||||
start: c0,
|
||||
end: c1,
|
||||
members: [member],
|
||||
intermediate: []
|
||||
}
|
||||
}else{
|
||||
} else {
|
||||
edge = {
|
||||
start : c1,
|
||||
start: c1,
|
||||
end: c0,
|
||||
members: [member],
|
||||
intermediate: []
|
||||
}
|
||||
}
|
||||
allEdgesByKey.set(key, edge)
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Lets merge them back together!
|
||||
|
||||
|
||||
let didMergeSomething = false;
|
||||
let allMergedEdges = Array.from(allEdgesByKey.values())
|
||||
const allEdgesByStartPoint = new Map<string, edge[]>()
|
||||
for (const edge of allMergedEdges) {
|
||||
|
||||
|
||||
edge.members.sort((m0, m1) => m0.index - m1.index)
|
||||
|
||||
const kstart = edge.start+""
|
||||
if(!allEdgesByStartPoint.has(kstart)){
|
||||
|
||||
const kstart = edge.start + ""
|
||||
if (!allEdgesByStartPoint.has(kstart)) {
|
||||
allEdgesByStartPoint.set(kstart, [])
|
||||
}
|
||||
allEdgesByStartPoint.get(kstart).push(edge)
|
||||
}
|
||||
|
||||
|
||||
function membersAreCompatible(first:edge, second:edge): boolean{
|
||||
|
||||
|
||||
function membersAreCompatible(first: edge, second: edge): boolean {
|
||||
// There must be an exact match between the members
|
||||
if(first.members === second.members){
|
||||
if (first.members === second.members) {
|
||||
return true
|
||||
}
|
||||
|
||||
if(first.members.length !== second.members.length){
|
||||
|
||||
if (first.members.length !== second.members.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// Members are sorted and have the same length, so we can check quickly
|
||||
for (let i = 0; i < first.members.length; i++) {
|
||||
const m0 = first.members[i]
|
||||
const m1 = second.members[i]
|
||||
if(m0.index !== m1.index || m0.isReversed !== m1.isReversed){
|
||||
if (m0.index !== m1.index || m0.isReversed !== m1.isReversed) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Allrigth, they are the same, lets mark this permanently
|
||||
second.members = first.members
|
||||
return true
|
||||
|
||||
|
||||
}
|
||||
|
||||
do{
|
||||
|
||||
do {
|
||||
didMergeSomething = false
|
||||
// We use 'allMergedEdges' as our running list
|
||||
const consumed = new Set<edge>()
|
||||
for (const edge of allMergedEdges) {
|
||||
// Can we make this edge longer at the end?
|
||||
if(consumed.has(edge)){
|
||||
if (consumed.has(edge)) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
console.log("Considering edge", edge)
|
||||
const matchingEndEdges = allEdgesByStartPoint.get(edge.end+"")
|
||||
const matchingEndEdges = allEdgesByStartPoint.get(edge.end + "")
|
||||
console.log("Matchign endpoints:", matchingEndEdges)
|
||||
if(matchingEndEdges === undefined){
|
||||
if (matchingEndEdges === undefined) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
for (let i = 0; i < matchingEndEdges.length; i++){
|
||||
|
||||
|
||||
for (let i = 0; i < matchingEndEdges.length; i++) {
|
||||
const endEdge = matchingEndEdges[i];
|
||||
|
||||
if(consumed.has(endEdge)){
|
||||
|
||||
if (consumed.has(endEdge)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if(!membersAreCompatible(edge, endEdge)){
|
||||
|
||||
if (!membersAreCompatible(edge, endEdge)) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
// We can make the segment longer!
|
||||
didMergeSomething = true
|
||||
console.log("Merging ", edge, "with ", endEdge)
|
||||
|
@ -617,13 +520,169 @@ export class GeoOperations {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
allMergedEdges = allMergedEdges.filter(edge => !consumed.has(edge));
|
||||
|
||||
}while(didMergeSomething)
|
||||
|
||||
|
||||
} while (didMergeSomething)
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes points that do not contribute to the geometry from linestrings and the outer ring of polygons.
|
||||
* Returs a new copy of the feature
|
||||
* @param feature
|
||||
*/
|
||||
static removeOvernoding(feature: any) {
|
||||
if (feature.geometry.type !== "LineString" && feature.geometry.type !== "Polygon") {
|
||||
throw "Overnode removal is only supported on linestrings and polygons"
|
||||
}
|
||||
|
||||
const copy = {
|
||||
...feature,
|
||||
geometry: {...feature.geometry}
|
||||
}
|
||||
let coordinates: [number, number][]
|
||||
if (feature.geometry.type === "LineString") {
|
||||
coordinates = [...feature.geometry.coordinates]
|
||||
copy.geometry.coordinates = coordinates
|
||||
} else {
|
||||
coordinates = [...feature.geometry.coordinates[0]]
|
||||
copy.geometry.coordinates[0] = coordinates
|
||||
}
|
||||
|
||||
// inline replacement in the coordinates list
|
||||
for (let i = coordinates.length - 2; i >= 1; i--) {
|
||||
const coordinate = coordinates[i];
|
||||
const nextCoordinate = coordinates[i + 1]
|
||||
const prevCoordinate = coordinates[i - 1]
|
||||
|
||||
const distP = GeoOperations.distanceBetween(coordinate, prevCoordinate)
|
||||
if(distP < 0.1){
|
||||
coordinates.splice(i, 1)
|
||||
continue
|
||||
}
|
||||
|
||||
if(i == coordinates.length - 2){
|
||||
const distN = GeoOperations.distanceBetween(coordinate, nextCoordinate)
|
||||
if(distN < 0.1){
|
||||
coordinates.splice(i, 1)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
const bearingN = turf.bearing(coordinate, nextCoordinate)
|
||||
const bearingP = turf.bearing(prevCoordinate, coordinate)
|
||||
const diff = Math.abs(bearingN - bearingP)
|
||||
if (diff < 4) {
|
||||
// If the diff is low, this point is hardly relevant
|
||||
coordinates.splice(i, 1)
|
||||
} else if (360 - diff < 4) {
|
||||
// In case that the line is going south, e.g. bearingN = 179, bearingP = -179
|
||||
coordinates.splice(i, 1)
|
||||
}
|
||||
|
||||
}
|
||||
return copy;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the intersection between two features.
|
||||
* Returns the length if intersecting a linestring and a (multi)polygon (in meters), returns a surface area (in m²) if intersecting two (multi)polygons
|
||||
* Returns 0 if both are linestrings
|
||||
* Returns null if the features are not intersecting
|
||||
*/
|
||||
private static calculateInstersection(feature, otherFeature, featureBBox: BBox, otherFeatureBBox?: BBox): number {
|
||||
if (feature.geometry.type === "LineString") {
|
||||
|
||||
|
||||
otherFeatureBBox = otherFeatureBBox ?? BBox.get(otherFeature);
|
||||
const overlaps = featureBBox.overlapsWith(otherFeatureBBox)
|
||||
if (!overlaps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Calculate the length of the intersection
|
||||
|
||||
|
||||
let intersectionPoints = turf.lineIntersect(feature, otherFeature);
|
||||
if (intersectionPoints.features.length == 0) {
|
||||
// No intersections.
|
||||
// If one point is inside of the polygon, all points are
|
||||
|
||||
|
||||
const coors = feature.geometry.coordinates;
|
||||
const startCoor = coors[0]
|
||||
if (this.inside(startCoor, otherFeature)) {
|
||||
return this.lengthInMeters(feature)
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
let intersectionPointsArray = intersectionPoints.features.map(d => {
|
||||
return d.geometry.coordinates
|
||||
});
|
||||
|
||||
if (otherFeature.geometry.type === "LineString") {
|
||||
if (intersectionPointsArray.length > 0) {
|
||||
return 0
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (intersectionPointsArray.length == 1) {
|
||||
// We need to add the start- or endpoint of the current feature, depending on which one is embedded
|
||||
const coors = feature.geometry.coordinates;
|
||||
const startCoor = coors[0]
|
||||
if (this.inside(startCoor, otherFeature)) {
|
||||
// The startpoint is embedded
|
||||
intersectionPointsArray.push(startCoor)
|
||||
} else {
|
||||
intersectionPointsArray.push(coors[coors.length - 1])
|
||||
}
|
||||
}
|
||||
|
||||
let intersection = turf.lineSlice(turf.point(intersectionPointsArray[0]), turf.point(intersectionPointsArray[1]), feature);
|
||||
|
||||
if (intersection == null) {
|
||||
return null;
|
||||
}
|
||||
const intersectionSize = turf.length(intersection); // in km
|
||||
return intersectionSize * 1000
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (feature.geometry.type === "Polygon" || feature.geometry.type === "MultiPolygon") {
|
||||
const otherFeatureBBox = BBox.get(otherFeature);
|
||||
const overlaps = featureBBox.overlapsWith(otherFeatureBBox)
|
||||
if (!overlaps) {
|
||||
return null;
|
||||
}
|
||||
if (otherFeature.geometry.type === "LineString") {
|
||||
return this.calculateInstersection(otherFeature, feature, otherFeatureBBox, featureBBox)
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
const intersection = turf.intersect(feature, otherFeature);
|
||||
if (intersection == null) {
|
||||
return null;
|
||||
}
|
||||
return turf.area(intersection); // in m²
|
||||
} catch (e) {
|
||||
if (e.message === "Each LinearRing of a Polygon must have 4 or more Positions.") {
|
||||
// WORKAROUND TIME!
|
||||
// See https://github.com/Turfjs/turf/pull/2238
|
||||
return null;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
}
|
||||
throw "CalculateIntersection fallthrough: can not calculate an intersection between features"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -118,18 +118,16 @@ export default class ReplaceGeometryAction extends OsmChangeAction {
|
|||
})
|
||||
|
||||
|
||||
|
||||
|
||||
reprojectedNodes.forEach(({newLat, newLon, nodeId}) => {
|
||||
reprojectedNodes.forEach(({newLat, newLon, nodeId}) => {
|
||||
|
||||
const origNode = allNodesById.get(nodeId);
|
||||
const feature = {
|
||||
const feature = {
|
||||
type: "Feature",
|
||||
properties: {
|
||||
"move": "yes",
|
||||
"reprojection":"yes",
|
||||
"reprojection": "yes",
|
||||
"osm-id": nodeId,
|
||||
"id": "replace-geometry-reproject-" + nodeId ,
|
||||
"id": "replace-geometry-reproject-" + nodeId,
|
||||
"original-node-tags": JSON.stringify(origNode.tags)
|
||||
},
|
||||
geometry: {
|
||||
|
@ -183,6 +181,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction {
|
|||
reprojectedNodes: Map<number, {
|
||||
/*Move the node with this ID into the way as extra node, as it has some relation with the original object*/
|
||||
projectAfterIndex: number,
|
||||
distance: number,
|
||||
newLat: number,
|
||||
newLon: number,
|
||||
nodeId: number
|
||||
|
@ -204,7 +203,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction {
|
|||
if (idN < 0 || type !== "way") {
|
||||
throw "Invalid ID to conflate: " + this.wayToReplaceId
|
||||
}
|
||||
const url = `${this.state.osmConnection._oauth_config.url}/api/0.6/${this.wayToReplaceId}/full`;
|
||||
const url = `${this.state.osmConnection?._oauth_config?.url ?? "https://openstreetmap.org"}/api/0.6/${this.wayToReplaceId}/full`;
|
||||
const rawData = await Utils.downloadJsonCached(url, 1000)
|
||||
parsed = OsmObject.ParseObjects(rawData.elements);
|
||||
}
|
||||
|
@ -355,6 +354,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction {
|
|||
const reprojectedNodes = new Map<number, {
|
||||
/*Move the node with this ID into the way as extra node, as it has some relation with the original object*/
|
||||
projectAfterIndex: number,
|
||||
distance: number,
|
||||
newLat: number,
|
||||
newLon: number,
|
||||
nodeId: number
|
||||
|
@ -384,18 +384,17 @@ export default class ReplaceGeometryAction extends OsmChangeAction {
|
|||
const projected = GeoOperations.nearestPoint(
|
||||
way, [node.lon, node.lat]
|
||||
)
|
||||
console.trace("Node "+id+" should be kept and projected to ", projected)
|
||||
|
||||
reprojectedNodes.set(id, {
|
||||
newLon: projected.geometry.coordinates[0],
|
||||
newLat: projected.geometry.coordinates[1],
|
||||
projectAfterIndex: projected.properties.index,
|
||||
distance: projected.properties.dist,
|
||||
nodeId: id
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
reprojectedNodes.forEach((_, nodeId) => unusedIds.delete(nodeId))
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -403,7 +402,6 @@ export default class ReplaceGeometryAction extends OsmChangeAction {
|
|||
}
|
||||
|
||||
protected async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> {
|
||||
throw "Use reprojectedNodes!" // TODO FIXME
|
||||
const nodeDb = this.state.featurePipeline.fullNodeDatabase;
|
||||
if (nodeDb === undefined) {
|
||||
throw "PANIC: replaceGeometryAction needs the FullNodeDatabase, which is undefined. This should be initialized by having the 'type_node'-layer enabled in your theme. (NB: the replacebutton has type_node as dependency)"
|
||||
|
@ -461,13 +459,46 @@ throw "Use reprojectedNodes!" // TODO FIXME
|
|||
allChanges.push(...await addExtraTags.CreateChangeDescriptions(changes))
|
||||
}
|
||||
|
||||
const newCoordinates = [...this.targetCoordinates]
|
||||
|
||||
{
|
||||
// Add reprojected nodes to the way
|
||||
|
||||
const proj = Array.from(reprojectedNodes.values())
|
||||
proj.sort((a, b) => {
|
||||
// Sort descending
|
||||
const diff = b.projectAfterIndex - a.projectAfterIndex;
|
||||
if(diff !== 0){
|
||||
return diff
|
||||
}
|
||||
return b.distance - a.distance;
|
||||
|
||||
|
||||
})
|
||||
|
||||
for (const reprojectedNode of proj) {
|
||||
const change = <ChangeDescription>{
|
||||
id: reprojectedNode.nodeId,
|
||||
type: "node",
|
||||
meta: {
|
||||
theme: this.theme,
|
||||
changeType: "move"
|
||||
},
|
||||
changes: {lon: reprojectedNode.newLon, lat: reprojectedNode.newLat}
|
||||
}
|
||||
allChanges.push(change)
|
||||
actualIdsToUse.splice(reprojectedNode.projectAfterIndex + 1, 0, reprojectedNode.nodeId)
|
||||
newCoordinates.splice(reprojectedNode.projectAfterIndex + 1, 0, [reprojectedNode.newLon, reprojectedNode.newLat])
|
||||
}
|
||||
}
|
||||
|
||||
// Actually change the nodes of the way!
|
||||
allChanges.push({
|
||||
type: "way",
|
||||
id: osmWay.id,
|
||||
changes: {
|
||||
nodes: actualIdsToUse,
|
||||
coordinates: this.targetCoordinates
|
||||
coordinates: newCoordinates
|
||||
},
|
||||
meta: {
|
||||
theme: this.theme,
|
||||
|
@ -477,8 +508,6 @@ throw "Use reprojectedNodes!" // TODO FIXME
|
|||
|
||||
|
||||
// Some nodes might need to be deleted
|
||||
const detachedNodeIds = Array.from(detachedNodes.keys());
|
||||
|
||||
if (detachedNodes.size > 0) {
|
||||
detachedNodes.forEach(({hasTags, reason}, nodeId) => {
|
||||
const parentWays = nodeDb.GetParentWays(nodeId)
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import {UIEventSource} from "../UIEventSource";
|
||||
import * as idb from "idb-keyval"
|
||||
import ScriptUtils from "../../scripts/ScriptUtils";
|
||||
import {Utils} from "../../Utils";
|
||||
/**
|
||||
* UIEventsource-wrapper around indexedDB key-value
|
||||
*/
|
||||
|
@ -8,6 +10,9 @@ export class IdbLocalStorage {
|
|||
|
||||
public static Get<T>(key: string, options: { defaultValue?: T }): UIEventSource<T>{
|
||||
const src = new UIEventSource<T>(options.defaultValue, "idb-local-storage:"+key)
|
||||
if(Utils.runningFromConsole){
|
||||
return src;
|
||||
}
|
||||
idb.get(key).then(v => src.setData(v ?? options.defaultValue))
|
||||
src.addCallback(v => idb.set(key, v))
|
||||
return src;
|
||||
|
|
|
@ -35,6 +35,7 @@ import ReplaceGeometryAction from "../../Logic/Osm/Actions/ReplaceGeometryAction
|
|||
import CreateMultiPolygonWithPointReuseAction from "../../Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction";
|
||||
import {Tag} from "../../Logic/Tags/Tag";
|
||||
import TagApplyButton from "./TagApplyButton";
|
||||
import {GeoOperations} from "../../Logic/GeoOperations";
|
||||
|
||||
|
||||
abstract class AbstractImportButton implements SpecialVisualizations {
|
||||
|
@ -308,8 +309,6 @@ export class ConflateButton extends AbstractImportButton {
|
|||
args: { max_snap_distance: string; snap_onto_layers: string; icon: string; text: string; tags: string; newTags: UIEventSource<Tag[]>; targetLayer: string },
|
||||
tagSource: UIEventSource<any>, guiState: DefaultGuiState, feature: any, onCancelClicked: () => void): BaseUIElement {
|
||||
|
||||
return new FixedUiElement("ReplaceGeometry is currently very broken - use mapcomplete.osm.be for now").SetClass("alert")
|
||||
|
||||
const nodesMustMatch = args.snap_onto_layers?.split(";")?.map((tag, i) => TagUtils.Tag(tag, "TagsSpec for import button " + i))
|
||||
|
||||
const mergeConfigs = []
|
||||
|
@ -325,6 +324,7 @@ export class ConflateButton extends AbstractImportButton {
|
|||
|
||||
const key = args["way_to_conflate"]
|
||||
const wayToConflate = tagSource.data[key]
|
||||
feature = GeoOperations.removeOvernoding(feature);
|
||||
const action = new ReplaceGeometryAction(
|
||||
state,
|
||||
feature,
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
"mappings": [
|
||||
{
|
||||
"if": "reprojection=yes",
|
||||
"then": "ring:#f00"
|
||||
"then": "none:#f00"
|
||||
},
|
||||
{
|
||||
"if": "move=no",
|
||||
|
@ -45,13 +45,15 @@
|
|||
},
|
||||
{
|
||||
"location": "start",
|
||||
"icon": "square:#f00",
|
||||
"mappings": [
|
||||
{
|
||||
"if": "reprojection=yes",
|
||||
"then": "ring:#f00"
|
||||
}
|
||||
],
|
||||
"icon": {
|
||||
"render":"square:#f00",
|
||||
"mappings": [
|
||||
{
|
||||
"if": "reprojection=yes",
|
||||
"then": "reload:#f00"
|
||||
}
|
||||
]
|
||||
},
|
||||
"iconSize": {
|
||||
"render": "10,10,center",
|
||||
"mappings": [
|
||||
|
|
79
assets/svg/none.svg
Normal file
79
assets/svg/none.svg
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="487.23px"
|
||||
height="487.23px"
|
||||
viewBox="0 0 487.23 487.23"
|
||||
style="enable-background:new 0 0 487.23 487.23;"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="none.svg"
|
||||
inkscape:version="1.1.1 (1:1.1+202109281949+c3084ef5ed)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs43" /><sodipodi:namedview
|
||||
id="namedview41"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:pageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.6624592"
|
||||
inkscape:cx="243.61501"
|
||||
inkscape:cy="243.91576"
|
||||
inkscape:current-layer="Capa_1" />
|
||||
|
||||
<g
|
||||
id="g10">
|
||||
</g>
|
||||
<g
|
||||
id="g12">
|
||||
</g>
|
||||
<g
|
||||
id="g14">
|
||||
</g>
|
||||
<g
|
||||
id="g16">
|
||||
</g>
|
||||
<g
|
||||
id="g18">
|
||||
</g>
|
||||
<g
|
||||
id="g20">
|
||||
</g>
|
||||
<g
|
||||
id="g22">
|
||||
</g>
|
||||
<g
|
||||
id="g24">
|
||||
</g>
|
||||
<g
|
||||
id="g26">
|
||||
</g>
|
||||
<g
|
||||
id="g28">
|
||||
</g>
|
||||
<g
|
||||
id="g30">
|
||||
</g>
|
||||
<g
|
||||
id="g32">
|
||||
</g>
|
||||
<g
|
||||
id="g34">
|
||||
</g>
|
||||
<g
|
||||
id="g36">
|
||||
</g>
|
||||
<g
|
||||
id="g38">
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -99,7 +99,9 @@
|
|||
"osmTags": "building~*",
|
||||
"maxCacheAge": 0
|
||||
},
|
||||
"calculatedTags": [],
|
||||
"calculatedTags": [
|
||||
"_surface:strict:=feat.get('_surface')"
|
||||
],
|
||||
"mapRendering": [
|
||||
{
|
||||
"width": {
|
||||
|
@ -488,14 +490,17 @@
|
|||
"calculatedTags": [
|
||||
"_overlaps_with_buildings=feat.overlapWith('osm-buildings').filter(f => f.feat.properties.id.indexOf('-') < 0)",
|
||||
"_overlaps_with=feat.get('_overlaps_with_buildings').filter(f => f.overlap > 1 /* square meter */ )[0] ?? ''",
|
||||
"_overlap_absolute=feat.get('_overlaps_with')?.overlap",
|
||||
"_overlap_percentage=Math.round(100 * feat.get('_overlap_absolute') / feat.get('_surface')) ",
|
||||
"_osm_obj:source:ref=feat.get('_overlaps_with')?.feat?.properties['source:geometry:ref']",
|
||||
"_osm_obj:id=feat.get('_overlaps_with')?.feat?.properties?.id",
|
||||
"_osm_obj:source:date=feat.get('_overlaps_with')?.feat?.properties['source:geometry:date'].replace(/\\//g, '-')",
|
||||
"_osm_obj:building=feat.get('_overlaps_with')?.feat?.properties?.building",
|
||||
"_osm_obj:id=feat.get('_overlaps_with')?.feat?.properties?.id",
|
||||
"_osm_obj:addr:street=(feat.get('_overlaps_with')?.feat?.properties ?? {})['addr:street']",
|
||||
"_osm_obj:addr:housenumber=(feat.get('_overlaps_with')?.feat?.properties ?? {})['addr:housenumber']",
|
||||
"_osm_obj:surface=(feat.get('_overlaps_with')?.feat?.properties ?? {})['_surface:strict']",
|
||||
|
||||
"_overlap_absolute=feat.get('_overlaps_with')?.overlap",
|
||||
"_reverse_overlap_percentage=Math.round(100 * feat.get('_overlap_absolute') / feat.get('_surface'))",
|
||||
"_overlap_percentage=Math.round(100 * feat.get('_overlap_absolute') / feat.get('_osm_obj:surface'))",
|
||||
"_grb_ref=feat.properties['source:geometry:entity'] + '/' + feat.properties['source:geometry:oidn']",
|
||||
"_imported_osm_object_found= feat.properties['_osm_obj:source:ref'] == feat.properties._grb_ref",
|
||||
"_grb_date=feat.properties['source:geometry:date'].replace(/\\//g,'-')",
|
||||
|
@ -510,24 +515,29 @@
|
|||
"render": "{import_way_button(osm-buildings,building=$building;man_made=$man_made; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber; building:min_level=$_building:min_level, Upload this building to OpenStreetMap,,_is_part_of_building=true,1,_moveable=true)}",
|
||||
"mappings": [
|
||||
{
|
||||
"#": "Hide import button if intersection with other objects are detected",
|
||||
"if": "_intersects_with_other_features~*",
|
||||
"then": "This GRB building intersects with the following features: {_intersects_with_other_features}.<br/>Fix the overlap and try again"
|
||||
},
|
||||
{
|
||||
"#": "Actually the same as below, except that the text shows 'add the address' too",
|
||||
"if": {
|
||||
"and": [
|
||||
"_overlap_percentage>50",
|
||||
"_reverse_overlap_percentage>50",
|
||||
"_overlaps_with!=",
|
||||
"_osm_obj:addr:street=",
|
||||
"_osm_obj:addr:housenumber=",
|
||||
"addr:street~*",
|
||||
"addr:housenumber~*"
|
||||
"addr:housenumber~*",
|
||||
"addr:street!:={_osm_obj:addr:street}",
|
||||
"addr:housenumber!:={_osm_obj:addr:housenumber}"
|
||||
]
|
||||
},
|
||||
"then": "{conflate_button(osm-buildings,building=$_target_building_type; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref; addr:street=$addr:street; addr:housenumber=$addr:housenumber, Replace the geometry in OpenStreetMap and add the address,,_osm_obj:id)}"
|
||||
},
|
||||
{
|
||||
"if": "_overlaps_with!=",
|
||||
"if": {
|
||||
"and":[
|
||||
"_overlap_percentage>50",
|
||||
"_reverse_overlap_percentage>50",
|
||||
"_overlaps_with!="]
|
||||
},
|
||||
"then": "{conflate_button(osm-buildings,building=$_target_building_type; source:geometry:date=$_grb_date; source:geometry:ref=$_grb_ref, Replace the geometry in OpenStreetMap,,_osm_obj:id)}"
|
||||
}
|
||||
]
|
||||
|
@ -588,7 +598,7 @@
|
|||
},
|
||||
{
|
||||
"id": "overlapping building type",
|
||||
"render": "<div>The overlapping <a href='https://osm.org/{_osm_obj:id}' target='_blank'>openstreetmap-building</a> is a <b>{_osm_obj:building}</b> and covers <b>{_overlap_percentage}%</b> of the GRB building<div><h3>GRB geometry:</h3>{minimap(21, id):height:10rem;border-radius:1rem;overflow:hidden}<h3>OSM geometry:</h3>{minimap(21,_osm_obj:id):height:10rem;border-radius:1rem;overflow:hidden}",
|
||||
"render": "<div>The overlapping <a href='https://osm.org/{_osm_obj:id}' target='_blank'>openstreetmap-building</a> is a <b>{_osm_obj:building}</b> and covers <b>{_overlap_percentage}%</b> of the GRB building.<br/>The GRB-building covers <b>{_reverse_overlap_percentage}%</b> of the OSM building<div><h3>GRB geometry:</h3>{minimap(21, id):height:10rem;border-radius:1rem;overflow:hidden}<h3>OSM geometry:</h3>{minimap(21,_osm_obj:id):height:10rem;border-radius:1rem;overflow:hidden}",
|
||||
"condition": "_overlaps_with!="
|
||||
},
|
||||
{
|
||||
|
|
273
test.ts
273
test.ts
|
@ -16,197 +16,110 @@ MinimapImplementation.initialize()
|
|||
|
||||
async function test() {
|
||||
|
||||
const coordinates = <[number, number][]>[
|
||||
[
|
||||
3.216690793633461,
|
||||
51.21474084112525
|
||||
],
|
||||
[
|
||||
3.2167256623506546,
|
||||
51.214696737309964
|
||||
],
|
||||
[
|
||||
3.2169999182224274,
|
||||
51.214768983537674
|
||||
],
|
||||
[
|
||||
3.2169650495052338,
|
||||
51.21480720678671
|
||||
],
|
||||
[
|
||||
3.2169368863105774,
|
||||
51.21480090625335
|
||||
],
|
||||
[
|
||||
3.2169489562511444,
|
||||
51.21478074454077
|
||||
],
|
||||
[
|
||||
3.216886594891548,
|
||||
51.214765203214625
|
||||
],
|
||||
[
|
||||
3.2168812304735184,
|
||||
51.21477192378873
|
||||
],
|
||||
[
|
||||
3.2168644666671753,
|
||||
51.214768983537674
|
||||
],
|
||||
[
|
||||
3.2168537378311157,
|
||||
51.21478746511261
|
||||
],
|
||||
[
|
||||
3.216690793633461,
|
||||
51.21474084112525
|
||||
]
|
||||
]
|
||||
|
||||
const wayId = "way/323230330";
|
||||
const targetFeature = {
|
||||
type: "Feature",
|
||||
properties: {},
|
||||
geometry: {
|
||||
type: "Polygon",
|
||||
coordinates: [coordinates]
|
||||
}
|
||||
"type": "Feature",
|
||||
"properties": {},
|
||||
"geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[
|
||||
4.483118100000016,
|
||||
51.028366499999706
|
||||
],
|
||||
[
|
||||
4.483135099999986,
|
||||
51.028325800000005
|
||||
],
|
||||
[
|
||||
4.483137700000021,
|
||||
51.02831960000019
|
||||
],
|
||||
[
|
||||
4.4831429000000025,
|
||||
51.0283205
|
||||
],
|
||||
[
|
||||
4.483262199999987,
|
||||
51.02834059999982
|
||||
],
|
||||
[
|
||||
4.483276700000019,
|
||||
51.028299999999746
|
||||
],
|
||||
[
|
||||
4.483342100000037,
|
||||
51.02830730000009
|
||||
],
|
||||
[
|
||||
4.483340700000012,
|
||||
51.028331299999934
|
||||
],
|
||||
[
|
||||
4.483346499999953,
|
||||
51.02833189999984
|
||||
],
|
||||
[
|
||||
4.483290600000001,
|
||||
51.028500699999846
|
||||
],
|
||||
[
|
||||
4.4833335999999635,
|
||||
51.02851150000015
|
||||
],
|
||||
[
|
||||
4.4833433000000475,
|
||||
51.028513999999944
|
||||
],
|
||||
[
|
||||
4.483312899999958,
|
||||
51.02857759999998
|
||||
],
|
||||
[
|
||||
4.483141100000033,
|
||||
51.02851780000015
|
||||
],
|
||||
[
|
||||
4.483193100000022,
|
||||
51.028409999999894
|
||||
],
|
||||
[
|
||||
4.483206100000019,
|
||||
51.02838310000014
|
||||
],
|
||||
[
|
||||
4.483118100000016,
|
||||
51.028366499999706
|
||||
]
|
||||
]
|
||||
]
|
||||
},
|
||||
"id": "https://betadata.grbosm.site/grb?bbox=498980.9206456306,6626173.107985358,499133.7947022009,6626325.98204193/30",
|
||||
"bbox": {
|
||||
"maxLat": 51.02857759999998,
|
||||
"maxLon": 4.483346499999953,
|
||||
"minLat": 51.028299999999746,
|
||||
"minLon": 4.483118100000016
|
||||
},
|
||||
"_lon": 4.483232299999985,
|
||||
"_lat": 51.02843879999986
|
||||
}
|
||||
/*
|
||||
Utils.injectJsonDownloadForTests(
|
||||
"https://www.openstreetmap.org/api/0.6/way/160909312/full",
|
||||
{
|
||||
"version": "0.6",
|
||||
"generator": "CGImap 0.8.5 (920083 spike-06.openstreetmap.org)",
|
||||
"copyright": "OpenStreetMap and contributors",
|
||||
"attribution": "http://www.openstreetmap.org/copyright",
|
||||
"license": "http://opendatacommons.org/licenses/odbl/1-0/",
|
||||
"elements": [{
|
||||
"type": "node",
|
||||
"id": 1728823481,
|
||||
"lat": 51.2146969,
|
||||
"lon": 3.2167247,
|
||||
"timestamp": "2017-07-18T22:52:45Z",
|
||||
"version": 2,
|
||||
"changeset": 50391526,
|
||||
"user": "catweazle67",
|
||||
"uid": 1976209
|
||||
}, {
|
||||
"type": "node",
|
||||
"id": 1728823514,
|
||||
"lat": 51.2147863,
|
||||
"lon": 3.2168551,
|
||||
"timestamp": "2017-07-18T22:52:45Z",
|
||||
"version": 2,
|
||||
"changeset": 50391526,
|
||||
"user": "catweazle67",
|
||||
"uid": 1976209
|
||||
}, {
|
||||
"type": "node",
|
||||
"id": 1728823549,
|
||||
"lat": 51.2147399,
|
||||
"lon": 3.2168871,
|
||||
"timestamp": "2017-07-18T22:52:46Z",
|
||||
"version": 2,
|
||||
"changeset": 50391526,
|
||||
"user": "catweazle67",
|
||||
"uid": 1976209
|
||||
}, {
|
||||
"type": "node",
|
||||
"id": 4978288381,
|
||||
"lat": 51.2147638,
|
||||
"lon": 3.2168856,
|
||||
"timestamp": "2017-07-18T22:52:21Z",
|
||||
"version": 1,
|
||||
"changeset": 50391526,
|
||||
"user": "catweazle67",
|
||||
"uid": 1976209
|
||||
}, {
|
||||
"type": "node",
|
||||
"id": 4978289383,
|
||||
"lat": 51.2147676,
|
||||
"lon": 3.2169973,
|
||||
"timestamp": "2017-07-18T22:52:21Z",
|
||||
"version": 1,
|
||||
"changeset": 50391526,
|
||||
"user": "catweazle67",
|
||||
"uid": 1976209
|
||||
}, {
|
||||
"type": "node",
|
||||
"id": 4978289384,
|
||||
"lat": 51.2147683,
|
||||
"lon": 3.2168674,
|
||||
"timestamp": "2017-07-18T22:52:21Z",
|
||||
"version": 1,
|
||||
"changeset": 50391526,
|
||||
"user": "catweazle67",
|
||||
"uid": 1976209
|
||||
}, {
|
||||
"type": "node",
|
||||
"id": 4978289386,
|
||||
"lat": 51.2147718,
|
||||
"lon": 3.2168815,
|
||||
"timestamp": "2017-07-18T22:52:21Z",
|
||||
"version": 1,
|
||||
"changeset": 50391526,
|
||||
"user": "catweazle67",
|
||||
"uid": 1976209
|
||||
}, {
|
||||
"type": "node",
|
||||
"id": 4978289388,
|
||||
"lat": 51.2147884,
|
||||
"lon": 3.2169829,
|
||||
"timestamp": "2017-07-18T22:52:21Z",
|
||||
"version": 1,
|
||||
"changeset": 50391526,
|
||||
"user": "catweazle67",
|
||||
"uid": 1976209
|
||||
}, {
|
||||
"type": "way",
|
||||
"id": 160909312,
|
||||
"timestamp": "2017-07-18T22:52:30Z",
|
||||
"version": 2,
|
||||
"changeset": 50391526,
|
||||
"user": "catweazle67",
|
||||
"uid": 1976209,
|
||||
"nodes": [1728823483, 1728823514, 4978289384, 4978289386, 4978288381, 4978289388, 4978289383, 1728823549, 1728823481, 1728823483],
|
||||
"tags": {
|
||||
"addr:city": "Brugge",
|
||||
"addr:country": "BE",
|
||||
"addr:housenumber": "108",
|
||||
"addr:postcode": "8000",
|
||||
"addr:street": "Ezelstraat",
|
||||
"building": "yes"
|
||||
}
|
||||
}]
|
||||
}
|
||||
)
|
||||
*/
|
||||
const wayId = "way/160909312"
|
||||
|
||||
|
||||
const layout = AllKnownLayouts.allKnownLayouts.get("grb")
|
||||
const state = new State(layout)
|
||||
State.state = state;
|
||||
const bbox = new BBox(
|
||||
[[
|
||||
3.216193914413452,
|
||||
51.214585847530635
|
||||
4.482952281832695,
|
||||
51.02828527958197
|
||||
],
|
||||
[
|
||||
3.217325806617737,
|
||||
51.214585847530635
|
||||
],
|
||||
[
|
||||
3.217325806617737,
|
||||
51.21523102068533
|
||||
],
|
||||
[
|
||||
3.216193914413452,
|
||||
51.21523102068533
|
||||
],
|
||||
[
|
||||
3.216193914413452,
|
||||
51.214585847530635
|
||||
4.483400881290436,
|
||||
51.028578384406984
|
||||
]
|
||||
|
||||
])
|
||||
const url = `https://www.openstreetmap.org/api/0.6/map.json?bbox=${bbox.minLon},${bbox.minLat},${bbox.maxLon},${bbox.maxLat}`
|
||||
const data = await Utils.downloadJson(url)
|
||||
|
|
|
@ -362,6 +362,85 @@ export default class GeoOperationsSpec extends T {
|
|||
const overlapsRev = GeoOperations.calculateOverlap(polyHouse, [polyGrb])
|
||||
Assert.equal(overlapsRev.length, 0)
|
||||
|
||||
}],
|
||||
["Overnode removal test", () => {
|
||||
|
||||
const feature = { "geometry": {
|
||||
"type": "Polygon",
|
||||
"coordinates": [
|
||||
[
|
||||
[
|
||||
4.477944199999975,
|
||||
51.02783550000022
|
||||
],
|
||||
[
|
||||
4.477987899999996,
|
||||
51.027818800000034
|
||||
],
|
||||
[
|
||||
4.478004500000021,
|
||||
51.02783399999988
|
||||
],
|
||||
[
|
||||
4.478025499999962,
|
||||
51.02782489999994
|
||||
],
|
||||
[
|
||||
4.478079099999993,
|
||||
51.027873899999896
|
||||
],
|
||||
[
|
||||
4.47801040000006,
|
||||
51.027903799999955
|
||||
],
|
||||
[
|
||||
4.477964799999972,
|
||||
51.02785709999982
|
||||
],
|
||||
[
|
||||
4.477964699999964,
|
||||
51.02785690000006
|
||||
],
|
||||
[
|
||||
4.477944199999975,
|
||||
51.02783550000022
|
||||
]
|
||||
]
|
||||
]
|
||||
}}
|
||||
|
||||
const copy = GeoOperations.removeOvernoding(feature)
|
||||
Assert.equal(copy.geometry.coordinates[0].length, 7)
|
||||
T.listIdentical([
|
||||
[
|
||||
4.477944199999975,
|
||||
51.02783550000022
|
||||
],
|
||||
[
|
||||
4.477987899999996,
|
||||
51.027818800000034
|
||||
],
|
||||
[
|
||||
4.478004500000021,
|
||||
51.02783399999988
|
||||
],
|
||||
[
|
||||
4.478025499999962,
|
||||
51.02782489999994
|
||||
],
|
||||
[
|
||||
4.478079099999993,
|
||||
51.027873899999896
|
||||
],
|
||||
[
|
||||
4.47801040000006,
|
||||
51.027903799999955
|
||||
],
|
||||
[
|
||||
4.477944199999975,
|
||||
51.02783550000022
|
||||
]
|
||||
], copy.geometry.coordinates[0])
|
||||
}]
|
||||
]
|
||||
)
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -45,7 +45,9 @@ export default class T {
|
|||
throw `ListIdentical failed: expected a list of length ${expected.length} but got a list of length ${actual.length}`
|
||||
}
|
||||
for (let i = 0; i < expected.length; i++) {
|
||||
if (expected[i] !== actual[i]) {
|
||||
if(expected[i]["length"] !== undefined ){
|
||||
T.listIdentical(<any> expected[i], <any> actual[i])
|
||||
}else if (expected[i] !== actual[i]) {
|
||||
throw `ListIdentical failed at index ${i}: expected ${expected[i]} but got ${actual[i]}`
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue