forked from MapComplete/MapComplete
		
	Fix various bugs
This commit is contained in:
		
							parent
							
								
									30f4be183e
								
							
						
					
					
						commit
						5284f198d8
					
				
					 26 changed files with 339 additions and 119 deletions
				
			
		|  | @ -528,7 +528,7 @@ function stackHists<K, V>(hists: [V, Histogram<K>][]): [V, Histogram<K>][] { | ||||||
|         runningTotals.bumpHist(hist) |         runningTotals.bumpHist(hist) | ||||||
|         result.push([vhist[0], clone]) |         result.push([vhist[0], clone]) | ||||||
|     }) |     }) | ||||||
|     result.reverse() |     result.reverse(/* Changes in place, safe copy*/) | ||||||
|     return result |     return result | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -239,7 +239,7 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL | ||||||
|                     prefered = preferedCategory.data; |                     prefered = preferedCategory.data; | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 prefered.reverse(); |                 prefered.reverse(/*New list, inplace reverse is fine*/); | ||||||
|                 for (const category of prefered) { |                 for (const category of prefered) { | ||||||
|                     //Then sort all 'photo'-layers to the top. Stability of the sorting will force a 'best' photo layer on top
 |                     //Then sort all 'photo'-layers to the top. Stability of the sorting will force a 'best' photo layer on top
 | ||||||
|                     available.sort((a, b) => { |                     available.sort((a, b) => { | ||||||
|  |  | ||||||
|  | @ -75,7 +75,7 @@ export default class FeaturePipeline { | ||||||
|         this.state = state; |         this.state = state; | ||||||
| 
 | 
 | ||||||
|         const self = this |         const self = this | ||||||
|         const expiryInSeconds = Math.min(...state.layoutToUse.layers.map(l => l.maxAgeOfCache)) |         const expiryInSeconds = Math.min(...state.layoutToUse?.layers?.map(l => l.maxAgeOfCache) ?? []) | ||||||
|         this.oldestAllowedDate = new Date(new Date().getTime() - expiryInSeconds); |         this.oldestAllowedDate = new Date(new Date().getTime() - expiryInSeconds); | ||||||
|         this.osmSourceZoomLevel = state.osmApiTileSize.data; |         this.osmSourceZoomLevel = state.osmApiTileSize.data; | ||||||
|         const useOsmApi = state.locationControl.map(l => l.zoom > (state.overpassMaxZoom.data ?? 12)) |         const useOsmApi = state.locationControl.map(l => l.zoom > (state.overpassMaxZoom.data ?? 12)) | ||||||
|  |  | ||||||
|  | @ -74,7 +74,7 @@ export default class ChangeGeometryApplicator implements FeatureSourceForLayer { | ||||||
|             // We only apply the last change as that one'll have the latest geometry
 |             // We only apply the last change as that one'll have the latest geometry
 | ||||||
|             const change = changesForFeature[changesForFeature.length - 1] |             const change = changesForFeature[changesForFeature.length - 1] | ||||||
|             copy.feature.geometry = ChangeDescriptionTools.getGeojsonGeometry(change) |             copy.feature.geometry = ChangeDescriptionTools.getGeojsonGeometry(change) | ||||||
|             console.log("Applying a geometry change onto ", feature, change, copy) |             console.log("Applying a geometry change onto:", feature,"The change is:", change,"which becomes:", copy) | ||||||
|             newFeatures.push(copy) |             newFeatures.push(copy) | ||||||
|         } |         } | ||||||
|         this.features.setData(newFeatures) |         this.features.setData(newFeatures) | ||||||
|  |  | ||||||
|  | @ -79,7 +79,7 @@ export default class OsmFeatureSource { | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         const neededLayers = options.state.layoutToUse.layers |         const neededLayers = (options.state.layoutToUse?.layers ?? []) | ||||||
|             .filter(layer => !layer.doNotDownload) |             .filter(layer => !layer.doNotDownload) | ||||||
|             .filter(layer => layer.source.geojsonSource === undefined || layer.source.isOsmCacheLayer) |             .filter(layer => layer.source.geojsonSource === undefined || layer.source.isOsmCacheLayer) | ||||||
|         this.allowedTags = new Or(neededLayers.map(l => l.source.osmTags)) |         this.allowedTags = new Or(neededLayers.map(l => l.source.osmTags)) | ||||||
|  |  | ||||||
|  | @ -81,7 +81,7 @@ export class ChangeDescriptionTools { | ||||||
|             case "way": |             case "way": | ||||||
|                 const w = new OsmWay(change.id) |                 const w = new OsmWay(change.id) | ||||||
|                 w.nodes = change.changes["nodes"] |                 w.nodes = change.changes["nodes"] | ||||||
|                 w.coordinates = change.changes["coordinates"].map(coor => coor.reverse()) |                 w.coordinates = change.changes["coordinates"].map(([lon, lat]) => [lat, lon]) | ||||||
|                 return w.asGeoJson().geometry |                 return w.asGeoJson().geometry | ||||||
|             case "relation": |             case "relation": | ||||||
|                 const r = new OsmRelation(change.id) |                 const r = new OsmRelation(change.id) | ||||||
|  |  | ||||||
|  | @ -33,12 +33,12 @@ export default class CreateMultiPolygonWithPointReuseAction extends OsmCreateAct | ||||||
|         super(null, true); |         super(null, true); | ||||||
|         this._tags = [...tags, new Tag("type", "multipolygon")]; |         this._tags = [...tags, new Tag("type", "multipolygon")]; | ||||||
|         this.changeType = changeType; |         this.changeType = changeType; | ||||||
|         this.theme = state.layoutToUse.id |         this.theme = state?.layoutToUse?.id ?? "" | ||||||
|         this.createOuterWay = new CreateWayWithPointReuseAction([], outerRingCoordinates, state, config) |         this.createOuterWay = new CreateWayWithPointReuseAction([], outerRingCoordinates, state, config) | ||||||
|         this.createInnerWays = innerRingsCoordinates.map(ringCoordinates => |         this.createInnerWays = innerRingsCoordinates.map(ringCoordinates => | ||||||
|             new CreateNewWayAction([], |             new CreateNewWayAction([], | ||||||
|                 ringCoordinates.map(([lon, lat]) => ({lat, lon})), |                 ringCoordinates.map(([lon, lat]) => ({lat, lon})), | ||||||
|                 {theme: state.layoutToUse.id})) |                 {theme: state?.layoutToUse?.id})) | ||||||
| 
 | 
 | ||||||
|         this.geojsonPreview = { |         this.geojsonPreview = { | ||||||
|             type: "Feature", |             type: "Feature", | ||||||
|  |  | ||||||
|  | @ -112,16 +112,25 @@ export default class CreateNewNodeAction extends OsmCreateAction { | ||||||
| 
 | 
 | ||||||
|         const geojson = this._snapOnto.asGeoJson() |         const geojson = this._snapOnto.asGeoJson() | ||||||
|         const projected = GeoOperations.nearestPoint(geojson, [this._lon, this._lat]) |         const projected = GeoOperations.nearestPoint(geojson, [this._lon, this._lat]) | ||||||
|  |        const projectedCoor=     <[number, number]>projected.geometry.coordinates | ||||||
|         const index = projected.properties.index |         const index = projected.properties.index | ||||||
|         // We check that it isn't close to an already existing point
 |         // We check that it isn't close to an already existing point
 | ||||||
|         let reusedPointId = undefined; |         let reusedPointId = undefined; | ||||||
|         const prev = <[number, number]>geojson.geometry.coordinates[index] |         let outerring : [number,number][]; | ||||||
|         if (GeoOperations.distanceBetween(prev, <[number, number]>projected.geometry.coordinates) < this._reusePointDistance) { |          | ||||||
|  |         if(geojson.geometry.type === "LineString"){ | ||||||
|  |            outerring = <[number, number][]> geojson.geometry.coordinates | ||||||
|  |         }else if(geojson.geometry.type === "Polygon"){ | ||||||
|  |            outerring =<[number, number][]>  geojson.geometry.coordinates[0] | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         const prev= outerring[index] | ||||||
|  |         if (GeoOperations.distanceBetween(prev, projectedCoor) < this._reusePointDistance) { | ||||||
|             // We reuse this point instead!
 |             // We reuse this point instead!
 | ||||||
|             reusedPointId = this._snapOnto.nodes[index] |             reusedPointId = this._snapOnto.nodes[index] | ||||||
|         } |         } | ||||||
|         const next = <[number, number]>geojson.geometry.coordinates[index + 1] |         const next = outerring[index + 1] | ||||||
|         if (GeoOperations.distanceBetween(next, <[number, number]>projected.geometry.coordinates) < this._reusePointDistance) { |         if (GeoOperations.distanceBetween(next, projectedCoor) < this._reusePointDistance) { | ||||||
|             // We reuse this point instead!
 |             // We reuse this point instead!
 | ||||||
|             reusedPointId = this._snapOnto.nodes[index + 1] |             reusedPointId = this._snapOnto.nodes[index + 1] | ||||||
|         } |         } | ||||||
|  | @ -135,8 +144,7 @@ export default class CreateNewNodeAction extends OsmCreateAction { | ||||||
|             }] |             }] | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const locations = [...this._snapOnto.coordinates] |         const locations = [...this._snapOnto.coordinates.map(([lat, lon]) =><[number,number]> [lon, lat])] | ||||||
|         locations.forEach(coor => coor.reverse()) |  | ||||||
|         const ids = [...this._snapOnto.nodes] |         const ids = [...this._snapOnto.nodes] | ||||||
| 
 | 
 | ||||||
|         locations.splice(index + 1, 0, [this._lon, this._lat]) |         locations.splice(index + 1, 0, [this._lon, this._lat]) | ||||||
|  |  | ||||||
|  | @ -33,7 +33,7 @@ export default class CreateNewWayAction extends OsmCreateAction { | ||||||
|                 We filter those here, as the CreateWayWithPointReuseAction delegates the actual creation to here. |                 We filter those here, as the CreateWayWithPointReuseAction delegates the actual creation to here. | ||||||
|                 Filtering here also prevents similar bugs in other actions |                 Filtering here also prevents similar bugs in other actions | ||||||
|              */ |              */ | ||||||
|             if(this.coordinates.length > 0 && this.coordinates[this.coordinates.length - 1].nodeId === coordinate.nodeId){ |             if(this.coordinates.length > 0 && coordinate.nodeId !== undefined && this.coordinates[this.coordinates.length - 1].nodeId === coordinate.nodeId){ | ||||||
|                 // This is a duplicate id
 |                 // This is a duplicate id
 | ||||||
|                 console.warn("Skipping a node in createWay to avoid a duplicate node:", coordinate,"\nThe previous coordinates are: ", this.coordinates) |                 console.warn("Skipping a node in createWay to avoid a duplicate node:", coordinate,"\nThe previous coordinates are: ", this.coordinates) | ||||||
|                 continue |                 continue | ||||||
|  |  | ||||||
|  | @ -186,7 +186,7 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> { |     public async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> { | ||||||
|         const theme = this._state.layoutToUse.id |         const theme = this._state?.layoutToUse?.id | ||||||
|         const allChanges: ChangeDescription[] = [] |         const allChanges: ChangeDescription[] = [] | ||||||
|         const nodeIdsToUse: { lat: number, lon: number, nodeId?: number }[] = [] |         const nodeIdsToUse: { lat: number, lon: number, nodeId?: number }[] = [] | ||||||
|         for (let i = 0; i < this._coordinateInfo.length; i++) { |         for (let i = 0; i < this._coordinateInfo.length; i++) { | ||||||
|  | @ -251,7 +251,7 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction { | ||||||
| 
 | 
 | ||||||
|         const bbox = new BBox(coordinates) |         const bbox = new BBox(coordinates) | ||||||
|         const state = this._state |         const state = this._state | ||||||
|         const allNodes = [].concat(...state.featurePipeline.GetFeaturesWithin("type_node", bbox.pad(1.2))) |         const allNodes = [].concat(...state?.featurePipeline?.GetFeaturesWithin("type_node", bbox.pad(1.2))??[]) | ||||||
|         const maxDistance = Math.max(...this._config.map(c => c.withinRangeOfM)) |         const maxDistance = Math.max(...this._config.map(c => c.withinRangeOfM)) | ||||||
| 
 | 
 | ||||||
|         // Init coordianteinfo with undefined but the same length as coordinates
 |         // Init coordianteinfo with undefined but the same length as coordinates
 | ||||||
|  |  | ||||||
|  | @ -28,6 +28,7 @@ export default class ReplaceGeometryAction extends OsmChangeAction { | ||||||
|     /** |     /** | ||||||
|      * The target coordinates that should end up in OpenStreetMap. |      * The target coordinates that should end up in OpenStreetMap. | ||||||
|      * This is identical to either this.feature.geometry.coordinates or -in case of a polygon- feature.geometry.coordinates[0] |      * This is identical to either this.feature.geometry.coordinates or -in case of a polygon- feature.geometry.coordinates[0] | ||||||
|  |      * Format: [lon, lat] | ||||||
|      */ |      */ | ||||||
|     private readonly targetCoordinates: [number, number][]; |     private readonly targetCoordinates: [number, number][]; | ||||||
|     /** |     /** | ||||||
|  | @ -540,8 +541,6 @@ export default class ReplaceGeometryAction extends OsmChangeAction { | ||||||
|                     id: nodeId, |                     id: nodeId, | ||||||
|                 }) |                 }) | ||||||
|             }) |             }) | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return allChanges |         return allChanges | ||||||
|  |  | ||||||
|  | @ -55,8 +55,8 @@ export class Changes { | ||||||
|         // This doesn't matter however, as the '-1' is per piecewise upload, not global per changeset
 |         // This doesn't matter however, as the '-1' is per piecewise upload, not global per changeset
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static createChangesetFor(csId: string, |     static createChangesetFor(csId: string, | ||||||
|                                       allChanges: { |                               allChanges: { | ||||||
|                                           modifiedObjects: OsmObject[], |                                           modifiedObjects: OsmObject[], | ||||||
|                                           newObjects: OsmObject[], |                                           newObjects: OsmObject[], | ||||||
|                                           deletedObjects: OsmObject[] |                                           deletedObjects: OsmObject[] | ||||||
|  |  | ||||||
|  | @ -207,27 +207,36 @@ export abstract class OsmObject { | ||||||
|         return objects; |         return objects; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Uses the list of polygon features to determine if the given tags are a polygon or not. | ||||||
|  |      * */ | ||||||
|     protected static isPolygon(tags: any): boolean { |     protected static isPolygon(tags: any): boolean { | ||||||
|         for (const tagsKey in tags) { |         for (const tagsKey in tags) { | ||||||
|             if (!tags.hasOwnProperty(tagsKey)) { |             if (!tags.hasOwnProperty(tagsKey)) { | ||||||
|                 continue |                 continue | ||||||
|             } |             } | ||||||
|             const polyGuide = OsmObject.polygonFeatures.get(tagsKey) |             const polyGuide : { values: Set<string>; blacklist: boolean } = OsmObject.polygonFeatures.get(tagsKey) | ||||||
|             if (polyGuide === undefined) { |             if (polyGuide === undefined) { | ||||||
|                 continue |                 continue | ||||||
|             } |             } | ||||||
|             if ((polyGuide.values === null)) { |             if ((polyGuide.values === null)) { | ||||||
|                 // We match all
 |                 // .values is null, thus merely _having_ this key is enough to be a polygon (or if blacklist, being a line)
 | ||||||
|                 return !polyGuide.blacklist |                 return !polyGuide.blacklist | ||||||
|             } |             } | ||||||
|             // is the key contained?
 |             // is the key contained? Then we have a match if the value is contained
 | ||||||
|             return polyGuide.values.has(tags[tagsKey]) |             const doesMatch = polyGuide.values.has(tags[tagsKey]) | ||||||
|  |             if(polyGuide.blacklist){ | ||||||
|  |                 return !doesMatch | ||||||
|  |             } | ||||||
|  |             return doesMatch | ||||||
|         } |         } | ||||||
|  |          | ||||||
|  |         return false; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private static constructPolygonFeatures(): Map<string, { values: Set<string>, blacklist: boolean }> { |     private static constructPolygonFeatures(): Map<string, { values: Set<string>, blacklist: boolean }> { | ||||||
|         const result = new Map<string, { values: Set<string>, blacklist: boolean }>(); |         const result = new Map<string, { values: Set<string>, blacklist: boolean }>(); | ||||||
|         for (const polygonFeature of polygon_features) { |         for (const polygonFeature of (polygon_features["default"] ?? polygon_features)) { | ||||||
|             const key = polygonFeature.key; |             const key = polygonFeature.key; | ||||||
| 
 | 
 | ||||||
|             if (polygonFeature.polygon === "all") { |             if (polygonFeature.polygon === "all") { | ||||||
|  | @ -381,7 +390,7 @@ export class OsmWay extends OsmObject { | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (element.nodes === undefined) { |         if (element.nodes === undefined) { | ||||||
|             console.log("PANIC") |             console.error("PANIC: no nodes!") | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         for (const nodeId of element.nodes) { |         for (const nodeId of element.nodes) { | ||||||
|  | @ -417,7 +426,9 @@ export class OsmWay extends OsmObject { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private isPolygon(): boolean { |     private isPolygon(): boolean { | ||||||
|         if (this.coordinates[0] !== this.coordinates[this.coordinates.length - 1]) { |         // Compare lat and lon seperately, as the coordinate array might not be a reference to the same object
 | ||||||
|  |         if (this.coordinates[0][0] !== this.coordinates[this.coordinates.length - 1][0] || | ||||||
|  |             this.coordinates[0][1] !== this.coordinates[this.coordinates.length - 1][1] ) { | ||||||
|             return false; // Not closed
 |             return false; // Not closed
 | ||||||
|         } |         } | ||||||
|         return OsmObject.isPolygon(this.tags) |         return OsmObject.isPolygon(this.tags) | ||||||
|  |  | ||||||
|  | @ -25,7 +25,7 @@ export default class FeaturePipelineState extends MapState { | ||||||
|     constructor(layoutToUse: LayoutConfig) { |     constructor(layoutToUse: LayoutConfig) { | ||||||
|         super(layoutToUse); |         super(layoutToUse); | ||||||
| 
 | 
 | ||||||
|         const clustering = layoutToUse.clustering |         const clustering = layoutToUse?.clustering | ||||||
|         this.featureAggregator = TileHierarchyAggregator.createHierarchy(this); |         this.featureAggregator = TileHierarchyAggregator.createHierarchy(this); | ||||||
|         const clusterCounter = this.featureAggregator |         const clusterCounter = this.featureAggregator | ||||||
|         const self = this; |         const self = this; | ||||||
|  |  | ||||||
|  | @ -117,10 +117,12 @@ export default class MapState extends UserRelatedState { | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         this.overlayToggles = this.layoutToUse.tileLayerSources.filter(c => c.name !== undefined).map(c => ({ |         this.overlayToggles = this.layoutToUse?.tileLayerSources | ||||||
|  |             ?.filter(c => c.name !== undefined) | ||||||
|  |             ?.map(c => ({ | ||||||
|             config: c, |             config: c, | ||||||
|             isDisplayed: QueryParameters.GetBooleanQueryParameter("overlay-" + c.id, c.defaultState, "Wether or not the overlay " + c.id + " is shown") |             isDisplayed: QueryParameters.GetBooleanQueryParameter("overlay-" + c.id, c.defaultState, "Wether or not the overlay " + c.id + " is shown") | ||||||
|         })) |         })) ?? [] | ||||||
|         this.filteredLayers = this.InitializeFilteredLayers() |         this.filteredLayers = this.InitializeFilteredLayers() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -142,7 +144,7 @@ export default class MapState extends UserRelatedState { | ||||||
|             initialized.add(overlayToggle.config) |             initialized.add(overlayToggle.config) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         for (const tileLayerSource of this.layoutToUse.tileLayerSources) { |         for (const tileLayerSource of this.layoutToUse?.tileLayerSources ?? []) { | ||||||
|             if (initialized.has(tileLayerSource)) { |             if (initialized.has(tileLayerSource)) { | ||||||
|                 continue |                 continue | ||||||
|             } |             } | ||||||
|  | @ -153,28 +155,14 @@ export default class MapState extends UserRelatedState { | ||||||
| 
 | 
 | ||||||
|     private lockBounds() { |     private lockBounds() { | ||||||
|         const layout = this.layoutToUse; |         const layout = this.layoutToUse; | ||||||
|         if (layout.lockLocation) { |         if (!layout?.lockLocation) { | ||||||
|             if (layout.lockLocation === true) { |             return; | ||||||
|                 const tile = Tiles.embedded_tile( |  | ||||||
|                     layout.startLat, |  | ||||||
|                     layout.startLon, |  | ||||||
|                     layout.startZoom - 1 |  | ||||||
|                 ); |  | ||||||
|                 const bounds = Tiles.tile_bounds(tile.z, tile.x, tile.y); |  | ||||||
|                 // We use the bounds to get a sense of distance for this zoom level
 |  | ||||||
|                 const latDiff = bounds[0][0] - bounds[1][0]; |  | ||||||
|                 const lonDiff = bounds[0][1] - bounds[1][1]; |  | ||||||
|                 layout.lockLocation = [ |  | ||||||
|                     [layout.startLat - latDiff, layout.startLon - lonDiff], |  | ||||||
|                     [layout.startLat + latDiff, layout.startLon + lonDiff], |  | ||||||
|                 ]; |  | ||||||
|             } |  | ||||||
|             console.warn("Locking the bounds to ", layout.lockLocation); |  | ||||||
|             this.mainMapObject.installBounds( |  | ||||||
|                 new BBox(layout.lockLocation), |  | ||||||
|                 this.featureSwitchIsTesting.data |  | ||||||
|             ) |  | ||||||
|         } |         } | ||||||
|  |         console.warn("Locking the bounds to ", layout.lockLocation); | ||||||
|  |         this.mainMapObject.installBounds( | ||||||
|  |             new BBox(layout.lockLocation), | ||||||
|  |             this.featureSwitchIsTesting.data | ||||||
|  |         ) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private initCurrentView() { |     private initCurrentView() { | ||||||
|  | @ -364,8 +352,10 @@ export default class MapState extends UserRelatedState { | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private InitializeFilteredLayers() { |     private InitializeFilteredLayers() { | ||||||
| 
 |  | ||||||
|         const layoutToUse = this.layoutToUse; |         const layoutToUse = this.layoutToUse; | ||||||
|  |         if(layoutToUse === undefined){ | ||||||
|  |             return new UIEventSource<FilteredLayer[]>([]) | ||||||
|  |         } | ||||||
|         const flayers: FilteredLayer[] = []; |         const flayers: FilteredLayer[] = []; | ||||||
|         for (const layer of layoutToUse.layers) { |         for (const layer of layoutToUse.layers) { | ||||||
|             let isDisplayed: UIEventSource<boolean> |             let isDisplayed: UIEventSource<boolean> | ||||||
|  |  | ||||||
|  | @ -127,7 +127,7 @@ export default class PointRenderingConfig extends WithContextLoader { | ||||||
|     public GetBaseIcon(tags?: any): BaseUIElement { |     public GetBaseIcon(tags?: any): BaseUIElement { | ||||||
|         tags = tags ?? {id: "node/-1"} |         tags = tags ?? {id: "node/-1"} | ||||||
|         const rotation = Utils.SubstituteKeys(this.rotation?.GetRenderValue(tags)?.txt ?? "0deg", tags) |         const rotation = Utils.SubstituteKeys(this.rotation?.GetRenderValue(tags)?.txt ?? "0deg", tags) | ||||||
|         const htmlDefs = Utils.SubstituteKeys(this.icon.GetRenderValue(tags)?.txt, tags) |         const htmlDefs = Utils.SubstituteKeys(this.icon?.GetRenderValue(tags)?.txt, tags) | ||||||
|         let defaultPin: BaseUIElement = undefined |         let defaultPin: BaseUIElement = undefined | ||||||
|         if (this.label === undefined) { |         if (this.label === undefined) { | ||||||
|             defaultPin = Svg.teardrop_with_hole_green_svg() |             defaultPin = Svg.teardrop_with_hole_green_svg() | ||||||
|  |  | ||||||
|  | @ -338,7 +338,8 @@ export default class TagRenderingConfig { | ||||||
| 
 | 
 | ||||||
|             const free = this.freeform?.key |             const free = this.freeform?.key | ||||||
|             if (free !== undefined) { |             if (free !== undefined) { | ||||||
|                 return tags[free] !== undefined |                 const value = tags[free] | ||||||
|  |                 return value !== undefined && value !== "" | ||||||
|             } |             } | ||||||
|             return false |             return false | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -93,7 +93,7 @@ export default class Histogram<T> extends VariableUiElement { | ||||||
|                     keys.sort() |                     keys.sort() | ||||||
|                     break; |                     break; | ||||||
|                 case "name-rev": |                 case "name-rev": | ||||||
|                     keys.sort().reverse() |                     keys.sort().reverse(/*Copy of array, inplace reverse if fine*/) | ||||||
|                     break; |                     break; | ||||||
|                 case "count": |                 case "count": | ||||||
|                     keys.sort((k0, k1) => counts.get(k0) - counts.get(k1)) |                     keys.sort((k0, k1) => counts.get(k0) - counts.get(k1)) | ||||||
|  |  | ||||||
|  | @ -543,9 +543,9 @@ class LengthTextField extends TextFieldDef { | ||||||
|         // Bit of a hack: we project the centerpoint to the closes point on the road - if available
 |         // Bit of a hack: we project the centerpoint to the closes point on the road - if available
 | ||||||
|         if (options?.feature !== undefined && options.feature.geometry.type !== "Point") { |         if (options?.feature !== undefined && options.feature.geometry.type !== "Point") { | ||||||
|             const lonlat = <[number, number]>[...options.location] |             const lonlat = <[number, number]>[...options.location] | ||||||
|             lonlat.reverse() |             lonlat.reverse(/*Changes a clone, this is safe */) | ||||||
|             options.location = <[number, number]>GeoOperations.nearestPoint(options.feature, lonlat).geometry.coordinates |             options.location = <[number, number]>GeoOperations.nearestPoint(options.feature, lonlat).geometry.coordinates | ||||||
|             options.location.reverse() |             options.location.reverse(/*Changes a clone, this is safe */) | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -373,7 +373,7 @@ export class ImportWayButton extends AbstractImportButton implements AutoAction | ||||||
|                 { |                 { | ||||||
|                     name: "max_snap_distance", |                     name: "max_snap_distance", | ||||||
|                     doc: "If the imported object is a LineString or (Multi)Polygon, already existing OSM-points will be reused to construct the geometry of the newly imported way", |                     doc: "If the imported object is a LineString or (Multi)Polygon, already existing OSM-points will be reused to construct the geometry of the newly imported way", | ||||||
|                     defaultValue: "5" |                     defaultValue: "0.05" | ||||||
|                 }, |                 }, | ||||||
|                 { |                 { | ||||||
|                     name: "move_osm_point_if", |                     name: "move_osm_point_if", | ||||||
|  | @ -381,7 +381,7 @@ export class ImportWayButton extends AbstractImportButton implements AutoAction | ||||||
|                 }, { |                 }, { | ||||||
|                 name: "max_move_distance", |                 name: "max_move_distance", | ||||||
|                 doc: "If an OSM-point is moved, the maximum amount of meters it is moved. Capped on 20m", |                 doc: "If an OSM-point is moved, the maximum amount of meters it is moved. Capped on 20m", | ||||||
|                 defaultValue: "1" |                 defaultValue: "0.05" | ||||||
|             }, { |             }, { | ||||||
|                 name: "snap_onto_layers", |                 name: "snap_onto_layers", | ||||||
|                 doc: "If no existing nearby point exists, but a line of a specified layer is closeby, snap to this layer instead", |                 doc: "If no existing nearby point exists, but a line of a specified layer is closeby, snap to this layer instead", | ||||||
|  | @ -406,24 +406,12 @@ export class ImportWayButton extends AbstractImportButton implements AutoAction | ||||||
|         AbstractImportButton.importedIds.add(originalFeatureTags.data.id) |         AbstractImportButton.importedIds.add(originalFeatureTags.data.id) | ||||||
|         const args = this.parseArgs(argument, originalFeatureTags) |         const args = this.parseArgs(argument, originalFeatureTags) | ||||||
|         const feature = state.allElements.ContainingFeatures.get(id) |         const feature = state.allElements.ContainingFeatures.get(id) | ||||||
|         console.log("Geometry to auto-import is:", feature) |  | ||||||
|         const geom = feature.geometry |  | ||||||
|         let coordinates: [number, number][] |  | ||||||
|         if (geom.type === "LineString") { |  | ||||||
|             coordinates = geom.coordinates |  | ||||||
|         } else if (geom.type === "Polygon") { |  | ||||||
|             coordinates = geom.coordinates[0] |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|         const mergeConfigs = this.GetMergeConfig(args); |         const mergeConfigs = this.GetMergeConfig(args); | ||||||
| 
 |         const action = ImportWayButton.CreateAction( | ||||||
|         const action = this.CreateAction( |  | ||||||
|             feature, |             feature, | ||||||
|             args, |             args, | ||||||
|             <FeaturePipelineState>state, |             <FeaturePipelineState>state, | ||||||
|             mergeConfigs, |             mergeConfigs | ||||||
|             coordinates |  | ||||||
|         ) |         ) | ||||||
|         await state.changes.applyAction(action) |         await state.changes.applyAction(action) | ||||||
|     } |     } | ||||||
|  | @ -455,18 +443,8 @@ export class ImportWayButton extends AbstractImportButton implements AutoAction | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|         // Upload the way to OSM
 |         // Upload the way to OSM
 | ||||||
|         const geom = feature.geometry |  | ||||||
|         let coordinates: [number, number][] |  | ||||||
|         if (geom.type === "LineString") { |  | ||||||
|             coordinates = geom.coordinates |  | ||||||
|         } else if (geom.type === "Polygon") { |  | ||||||
|             coordinates = geom.coordinates[0] |  | ||||||
|         } |  | ||||||
|         const mergeConfigs = this.GetMergeConfig(args); |         const mergeConfigs = this.GetMergeConfig(args); | ||||||
| 
 |         let action = ImportWayButton.CreateAction(feature, args, state, mergeConfigs); | ||||||
| 
 |  | ||||||
|         let action = this.CreateAction(feature, args, state, mergeConfigs, coordinates); |  | ||||||
| 
 |  | ||||||
|         return this.createConfirmPanelForWay( |         return this.createConfirmPanelForWay( | ||||||
|             state, |             state, | ||||||
|             args, |             args, | ||||||
|  | @ -508,14 +486,12 @@ export class ImportWayButton extends AbstractImportButton implements AutoAction | ||||||
|         return mergeConfigs; |         return mergeConfigs; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private CreateAction(feature, |     private static CreateAction(feature, | ||||||
|                          args: { max_snap_distance: string; snap_onto_layers: string; icon: string; text: string; tags: string; newTags: UIEventSource<any>; targetLayer: string }, |                          args: { max_snap_distance: string; snap_onto_layers: string; icon: string; text: string; tags: string; newTags: UIEventSource<any>; targetLayer: string }, | ||||||
|                          state: FeaturePipelineState, |                          state: FeaturePipelineState, | ||||||
|                          mergeConfigs: any[], |                          mergeConfigs: any[]) { | ||||||
|                          coordinates: [number, number][]) { |  | ||||||
| 
 |  | ||||||
|         const coors = feature.geometry.coordinates |         const coors = feature.geometry.coordinates | ||||||
|         if (feature.geometry.type === "Polygon" && coors.length > 1) { |         if ((feature.geometry.type === "Polygon" ) && coors.length > 1) { | ||||||
|             const outer = coors[0] |             const outer = coors[0] | ||||||
|             const inner = [...coors] |             const inner = [...coors] | ||||||
|             inner.splice(0, 1) |             inner.splice(0, 1) | ||||||
|  | @ -531,7 +507,7 @@ export class ImportWayButton extends AbstractImportButton implements AutoAction | ||||||
| 
 | 
 | ||||||
|             return new CreateWayWithPointReuseAction( |             return new CreateWayWithPointReuseAction( | ||||||
|                 args.newTags.data, |                 args.newTags.data, | ||||||
|                 coordinates, |                 coors, | ||||||
|                 state, |                 state, | ||||||
|                 mergeConfigs |                 mergeConfigs | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|  | @ -28,7 +28,6 @@ export class LoginToggle extends VariableUiElement { | ||||||
|         const login = new LoginButton(text, state) |         const login = new LoginButton(text, state) | ||||||
|         super( |         super( | ||||||
|             state.osmConnection.loadingStatus.map(osmConnectionState => { |             state.osmConnection.loadingStatus.map(osmConnectionState => { | ||||||
|                 console.trace("Current osm state is ", osmConnectionState) |  | ||||||
|                 if(osmConnectionState === "loading"){ |                 if(osmConnectionState === "loading"){ | ||||||
|                     return loading |                     return loading | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  | @ -107,7 +107,7 @@ export default class SplitRoadWizard extends Toggle { | ||||||
|                 .filter(p => GeoOperations.distanceBetween(p[0].geometry.coordinates, coordinates) < 5) |                 .filter(p => GeoOperations.distanceBetween(p[0].geometry.coordinates, coordinates) < 5) | ||||||
|                 .map(p => p[1]) |                 .map(p => p[1]) | ||||||
|                 .sort((a, b) => a - b) |                 .sort((a, b) => a - b) | ||||||
|                 .reverse() |                 .reverse(/*Copy/derived list, inplace reverse is fine*/) | ||||||
|             if (points.length > 0) { |             if (points.length > 0) { | ||||||
|                 for (const point of points) { |                 for (const point of points) { | ||||||
|                     splitPoints.data.splice(point, 1) |                     splitPoints.data.splice(point, 1) | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								Utils.ts
									
										
									
									
									
								
							
							
						
						
									
										1
									
								
								Utils.ts
									
										
									
									
									
								
							|  | @ -777,5 +777,6 @@ In the case that MapComplete is pointed to the testing grounds, the edit will be | ||||||
|             b: parseInt(hex.substr(5, 2), 16), |             b: parseInt(hex.substr(5, 2), 16), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |      | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -2,34 +2,48 @@ import T from "./TestHelper"; | ||||||
| import {exec} from "child_process"; | import {exec} from "child_process"; | ||||||
| 
 | 
 | ||||||
| export default class CodeQualitySpec extends T { | export default class CodeQualitySpec extends T { | ||||||
|  | 
 | ||||||
|     constructor() { |     constructor() { | ||||||
|         super([ |         super([ | ||||||
|             [ |             [ | ||||||
|                 "no constructor.name in compiled code", () => { |                 "no constructor.name in compiled code", () => { | ||||||
| 
 |                 CodeQualitySpec.detectInCode("constructor\\.name", "This is not allowed, as minification does erase names.") | ||||||
|                 const excludedDirs = [".git", "node_modules", "dist", ".cache", ".parcel-cache", "assets"] |             }], | ||||||
| 
 |             [ | ||||||
|                 exec("grep \"constructor.name\" -r . " + excludedDirs.map(d => "--exclude-dir=" + d).join(" "), ((error, stdout, stderr) => { |                 "no reverse in compiled code", () => { | ||||||
|                     if (error?.message?.startsWith("Command failed: grep")) { |                 CodeQualitySpec.detectInCode("reverse()", "Reverse is stateful and changes the source list. This often causes subtle bugs") | ||||||
|                         return; |             }] | ||||||
|                     } |  | ||||||
|                     if (error !== null) { |  | ||||||
|                         throw error |  | ||||||
| 
 |  | ||||||
|                     } |  | ||||||
|                     if (stderr !== "") { |  | ||||||
|                         throw stderr |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                     const found = stdout.split("\n").filter(s => s !== "").filter(s => s.startsWith("test/")); |  | ||||||
|                     if (found.length > 0) { |  | ||||||
|                         throw "Found a 'constructor.name' at " + found.join(", ") + ". This is not allowed, as minification does erase names." |  | ||||||
|                     } |  | ||||||
| 
 |  | ||||||
|                 })) |  | ||||||
| 
 |  | ||||||
|             } |  | ||||||
|             ] |  | ||||||
|         ]); |         ]); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      *  | ||||||
|  |      * @param forbidden: a GREP-regex. This means that '.' is a wildcard and should be escaped to match a literal dot | ||||||
|  |      * @param reason | ||||||
|  |      * @private | ||||||
|  |      */ | ||||||
|  |     private static detectInCode(forbidden: string, reason: string) { | ||||||
|  | 
 | ||||||
|  |         const excludedDirs = [".git", "node_modules", "dist", ".cache", ".parcel-cache", "assets"] | ||||||
|  | 
 | ||||||
|  |         exec("grep -n \"" + forbidden + "\" -r . " + excludedDirs.map(d => "--exclude-dir=" + d).join(" "), ((error, stdout, stderr) => { | ||||||
|  |             if (error?.message?.startsWith("Command failed: grep")) { | ||||||
|  |                 console.warn("Command failed!") | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             if (error !== null) { | ||||||
|  |                 throw error | ||||||
|  | 
 | ||||||
|  |             } | ||||||
|  |             if (stderr !== "") { | ||||||
|  |                 throw stderr | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             const found = stdout.split("\n").filter(s => s !== "").filter(s => !s.startsWith("./test/")); | ||||||
|  |             if (found.length > 0) { | ||||||
|  |                 throw `Found a '${forbidden}' at \n    ${found.join("\n     ")}.\n ${reason}` | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         })) | ||||||
|  |     } | ||||||
| } | } | ||||||
							
								
								
									
										219
									
								
								test/ImportMultiPolygon.spec.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								test/ImportMultiPolygon.spec.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,219 @@ | ||||||
|  | import T from "./TestHelper"; | ||||||
|  | import CreateMultiPolygonWithPointReuseAction from "../Logic/Osm/Actions/CreateMultiPolygonWithPointReuseAction"; | ||||||
|  | import { Tag } from "../Logic/Tags/Tag"; | ||||||
|  | import FeaturePipelineState from "../Logic/State/FeaturePipelineState"; | ||||||
|  | import { Changes } from "../Logic/Osm/Changes"; | ||||||
|  | import {ChangesetHandler} from "../Logic/Osm/ChangesetHandler"; | ||||||
|  | import * as Assert from "assert"; | ||||||
|  | 
 | ||||||
|  | export default class ImportMultiPolygonSpec extends T { | ||||||
|  |      | ||||||
|  |     constructor() { | ||||||
|  |         super([ | ||||||
|  |             ["Correct changeset", | ||||||
|  |             async () => { | ||||||
|  | 
 | ||||||
|  |                 const feature = { | ||||||
|  |                     "type": "Feature", | ||||||
|  |                     "properties": { | ||||||
|  |                         "osm_id": "41097039", | ||||||
|  |                         "size_grb_building": "1374.89", | ||||||
|  |                         "addr:housenumber": "53", | ||||||
|  |                         "addr:street": "Startelstraat", | ||||||
|  |                         "building": "house", | ||||||
|  |                         "source:geometry:entity": "Gbg", | ||||||
|  |                         "source:geometry:date": "2014-04-28", | ||||||
|  |                         "source:geometry:oidn": "150044", | ||||||
|  |                         "source:geometry:uidn": "5403181", | ||||||
|  |                         "H_DTM_MIN": "50.35", | ||||||
|  |                         "H_DTM_GEM": "50.97", | ||||||
|  |                         "H_DSM_MAX": "59.40", | ||||||
|  |                         "H_DSM_P99": "59.09", | ||||||
|  |                         "HN_MAX": "8.43", | ||||||
|  |                         "HN_P99": "8.12", | ||||||
|  |                         "detection_method": "derived from OSM landuse: farmyard", | ||||||
|  |                         "auto_target_landuse": "farmyard", | ||||||
|  |                         "size_source_landuse": "8246.28", | ||||||
|  |                         "auto_building": "farm", | ||||||
|  |                         "id": "41097039", | ||||||
|  |                         "_lat": "50.84633355000016", | ||||||
|  |                         "_lon": "5.262964150000011", | ||||||
|  |                         "_layer": "grb", | ||||||
|  |                         "_length": "185.06002152312757", | ||||||
|  |                         "_length:km": "0.2", | ||||||
|  |                         "_now:date": "2022-02-22", | ||||||
|  |                         "_now:datetime": "2022-02-22 10:15:51", | ||||||
|  |                         "_loaded:date": "2022-02-22", | ||||||
|  |                         "_loaded:datetime": "2022-02-22 10:15:51", | ||||||
|  |                         "_geometry:type": "Polygon", | ||||||
|  |                         "_intersects_with_other_features": "", | ||||||
|  |                         "_country": "be", | ||||||
|  |                         "_overlaps_with_buildings": "[]", | ||||||
|  |                         "_overlap_percentage": "null", | ||||||
|  |                         "_grb_date": "2014-04-28", | ||||||
|  |                         "_grb_ref": "Gbg/150044", | ||||||
|  |                         "_building:min_level": "", | ||||||
|  |                         "_surface": "548.1242491529038", | ||||||
|  |                         "_surface:ha": "0", | ||||||
|  |                         "_reverse_overlap_percentage": "null", | ||||||
|  |                         "_imported_osm_object_found": "false", | ||||||
|  |                         "_imported_osm_still_fresh": "false", | ||||||
|  |                         "_target_building_type": "house" | ||||||
|  |                     }, | ||||||
|  |                     "geometry": { | ||||||
|  |                         "type": "Polygon", | ||||||
|  |                         "coordinates": <[number, number][][]>[ | ||||||
|  |                             [ | ||||||
|  |                                 [ | ||||||
|  |                                     5.262684300000043, | ||||||
|  |                                     50.84624409999995 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.262777500000024, | ||||||
|  |                                     50.84620759999988 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.262798899999998, | ||||||
|  |                                     50.84621390000019 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.262999799999994, | ||||||
|  |                                     50.84619519999999 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.263107500000007, | ||||||
|  |                                     50.84618920000014 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.263115, | ||||||
|  |                                     50.84620990000026 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.26310279999998, | ||||||
|  |                                     50.84623050000014 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.263117999999977, | ||||||
|  |                                     50.846247400000166 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.263174599999989, | ||||||
|  |                                     50.84631019999971 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.263166999999989, | ||||||
|  |                                     50.84631459999995 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.263243999999979, | ||||||
|  |                                     50.84640239999989 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.2631607000000065, | ||||||
|  |                                     50.84643459999996 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.26313309999997, | ||||||
|  |                                     50.84640089999985 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.262907499999996, | ||||||
|  |                                     50.84647790000018 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.2628939999999576, | ||||||
|  |                                     50.846463699999774 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.262872100000033, | ||||||
|  |                                     50.846440700000294 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.262784699999991, | ||||||
|  |                                     50.846348899999924 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.262684300000043, | ||||||
|  |                                     50.84624409999995 | ||||||
|  |                                 ] | ||||||
|  |                             ], | ||||||
|  |                             [ | ||||||
|  |                                 [ | ||||||
|  |                                     5.262801899999976, | ||||||
|  |                                     50.84623269999982 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.2629535000000285, | ||||||
|  |                                     50.84638830000012 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.263070700000018, | ||||||
|  |                                     50.84634720000008 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.262998000000025, | ||||||
|  |                                     50.84626279999982 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.263066799999966, | ||||||
|  |                                     50.84623959999975 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.263064000000004, | ||||||
|  |                                     50.84623330000007 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.263009599999997, | ||||||
|  |                                     50.84623730000026 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.263010199999956, | ||||||
|  |                                     50.84621629999986 | ||||||
|  |                                 ], | ||||||
|  |                                 [ | ||||||
|  |                                     5.262801899999976, | ||||||
|  |                                     50.84623269999982 | ||||||
|  |                                 ] | ||||||
|  |                             ] | ||||||
|  |                         ] | ||||||
|  |                     }, | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 const innerRings = [...feature.geometry.coordinates] | ||||||
|  |                 innerRings.splice(0, 1) | ||||||
|  | 
 | ||||||
|  |                 const action = new CreateMultiPolygonWithPointReuseAction( | ||||||
|  |                     [new Tag("building", "yes")], | ||||||
|  |                     feature.geometry.coordinates[0], | ||||||
|  |                     innerRings, | ||||||
|  |                     undefined, | ||||||
|  |                     [], | ||||||
|  |                     "import" | ||||||
|  |                 ) | ||||||
|  |                 const descriptions = await action.Perform(new Changes()) | ||||||
|  | 
 | ||||||
|  |                 function getCoor(id: number): {lat: number, lon:number} { | ||||||
|  |                     return <any> descriptions.find(d => d.type === "node" && d.id === id).changes | ||||||
|  |                 } | ||||||
|  |                  | ||||||
|  |                 const ways= descriptions.filter(d => d.type === "way") | ||||||
|  |                 T.isTrue(ways[0].id == -18, "unexpected id") | ||||||
|  |                 T.isTrue(ways[1].id == -27, "unexpected id") | ||||||
|  |                 const outer = ways[0].changes["coordinates"] | ||||||
|  |                 const outerExpected =    [[5.262684300000043,50.84624409999995],[5.262777500000024,50.84620759999988],[5.262798899999998,50.84621390000019],[5.262999799999994,50.84619519999999],[5.263107500000007,50.84618920000014],[5.263115,50.84620990000026],[5.26310279999998,50.84623050000014],[5.263117999999977,50.846247400000166],[5.263174599999989,50.84631019999971],[5.263166999999989,50.84631459999995],[5.263243999999979,50.84640239999989],[5.2631607000000065,50.84643459999996],[5.26313309999997,50.84640089999985],[5.262907499999996,50.84647790000018],[5.2628939999999576,50.846463699999774],[5.262872100000033,50.846440700000294],[5.262784699999991,50.846348899999924],[5.262684300000043,50.84624409999995]] | ||||||
|  |                 T.listIdentical(feature.geometry.coordinates[0], outer) | ||||||
|  |                 const inner = ways[1].changes["coordinates"] | ||||||
|  |                 T.listIdentical(feature.geometry.coordinates[1], inner) | ||||||
|  |                 const members = <{type: string, role: string, ref: number}[]> descriptions.find(d => d.type === "relation").changes["members"] | ||||||
|  |                 T.isTrue(members[0].role == "outer", "incorrect role") | ||||||
|  |                 T.isTrue(members[1].role == "inner", "incorrect role") | ||||||
|  |                 T.isTrue(members[0].type == "way", "incorrect type") | ||||||
|  |                 T.isTrue(members[1].type == "way", "incorrect type") | ||||||
|  |                 T.isTrue(members[0].ref == -18, "incorrect id") | ||||||
|  |                 T.isTrue(members[1].ref == -27, "incorrect id") | ||||||
|  |         }] | ||||||
|  |         ]); | ||||||
|  |     } | ||||||
|  |      | ||||||
|  |      | ||||||
|  | } | ||||||
|  | @ -20,6 +20,7 @@ import CreateNoteImportLayerSpec from "./CreateNoteImportLayer.spec"; | ||||||
| import ValidatedTextFieldTranslationsSpec from "./ValidatedTextFieldTranslations.spec"; | import ValidatedTextFieldTranslationsSpec from "./ValidatedTextFieldTranslations.spec"; | ||||||
| import CreateCacheSpec from "./CreateCache.spec"; | import CreateCacheSpec from "./CreateCache.spec"; | ||||||
| import CodeQualitySpec from "./CodeQuality.spec"; | import CodeQualitySpec from "./CodeQuality.spec"; | ||||||
|  | import ImportMultiPolygonSpec from "./ImportMultiPolygon.spec"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| async function main() { | async function main() { | ||||||
|  | @ -43,7 +44,8 @@ async function main() { | ||||||
|         new CreateNoteImportLayerSpec(), |         new CreateNoteImportLayerSpec(), | ||||||
|         new ValidatedTextFieldTranslationsSpec(), |         new ValidatedTextFieldTranslationsSpec(), | ||||||
|         new CreateCacheSpec(), |         new CreateCacheSpec(), | ||||||
|         new CodeQualitySpec() |         new CodeQualitySpec(), | ||||||
|  |         new ImportMultiPolygonSpec() | ||||||
|     ] |     ] | ||||||
|     ScriptUtils.fixUtils(); |     ScriptUtils.fixUtils(); | ||||||
|     const realDownloadFunc = Utils.externalDownloadFunction; |     const realDownloadFunc = Utils.externalDownloadFunction; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue