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)
|
||||
result.push([vhist[0], clone])
|
||||
})
|
||||
result.reverse()
|
||||
result.reverse(/* Changes in place, safe copy*/)
|
||||
return result
|
||||
}
|
||||
|
||||
|
|
|
@ -239,7 +239,7 @@ export default class AvailableBaseLayersImplementation implements AvailableBaseL
|
|||
prefered = preferedCategory.data;
|
||||
}
|
||||
|
||||
prefered.reverse();
|
||||
prefered.reverse(/*New list, inplace reverse is fine*/);
|
||||
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
|
||||
available.sort((a, b) => {
|
||||
|
|
|
@ -75,7 +75,7 @@ export default class FeaturePipeline {
|
|||
this.state = state;
|
||||
|
||||
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.osmSourceZoomLevel = state.osmApiTileSize.data;
|
||||
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
|
||||
const change = changesForFeature[changesForFeature.length - 1]
|
||||
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)
|
||||
}
|
||||
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.source.geojsonSource === undefined || layer.source.isOsmCacheLayer)
|
||||
this.allowedTags = new Or(neededLayers.map(l => l.source.osmTags))
|
||||
|
|
|
@ -81,7 +81,7 @@ export class ChangeDescriptionTools {
|
|||
case "way":
|
||||
const w = new OsmWay(change.id)
|
||||
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
|
||||
case "relation":
|
||||
const r = new OsmRelation(change.id)
|
||||
|
|
|
@ -33,12 +33,12 @@ export default class CreateMultiPolygonWithPointReuseAction extends OsmCreateAct
|
|||
super(null, true);
|
||||
this._tags = [...tags, new Tag("type", "multipolygon")];
|
||||
this.changeType = changeType;
|
||||
this.theme = state.layoutToUse.id
|
||||
this.theme = state?.layoutToUse?.id ?? ""
|
||||
this.createOuterWay = new CreateWayWithPointReuseAction([], outerRingCoordinates, state, config)
|
||||
this.createInnerWays = innerRingsCoordinates.map(ringCoordinates =>
|
||||
new CreateNewWayAction([],
|
||||
ringCoordinates.map(([lon, lat]) => ({lat, lon})),
|
||||
{theme: state.layoutToUse.id}))
|
||||
{theme: state?.layoutToUse?.id}))
|
||||
|
||||
this.geojsonPreview = {
|
||||
type: "Feature",
|
||||
|
|
|
@ -112,16 +112,25 @@ export default class CreateNewNodeAction extends OsmCreateAction {
|
|||
|
||||
const geojson = this._snapOnto.asGeoJson()
|
||||
const projected = GeoOperations.nearestPoint(geojson, [this._lon, this._lat])
|
||||
const projectedCoor= <[number, number]>projected.geometry.coordinates
|
||||
const index = projected.properties.index
|
||||
// We check that it isn't close to an already existing point
|
||||
let reusedPointId = undefined;
|
||||
const prev = <[number, number]>geojson.geometry.coordinates[index]
|
||||
if (GeoOperations.distanceBetween(prev, <[number, number]>projected.geometry.coordinates) < this._reusePointDistance) {
|
||||
let outerring : [number,number][];
|
||||
|
||||
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!
|
||||
reusedPointId = this._snapOnto.nodes[index]
|
||||
}
|
||||
const next = <[number, number]>geojson.geometry.coordinates[index + 1]
|
||||
if (GeoOperations.distanceBetween(next, <[number, number]>projected.geometry.coordinates) < this._reusePointDistance) {
|
||||
const next = outerring[index + 1]
|
||||
if (GeoOperations.distanceBetween(next, projectedCoor) < this._reusePointDistance) {
|
||||
// We reuse this point instead!
|
||||
reusedPointId = this._snapOnto.nodes[index + 1]
|
||||
}
|
||||
|
@ -135,8 +144,7 @@ export default class CreateNewNodeAction extends OsmCreateAction {
|
|||
}]
|
||||
}
|
||||
|
||||
const locations = [...this._snapOnto.coordinates]
|
||||
locations.forEach(coor => coor.reverse())
|
||||
const locations = [...this._snapOnto.coordinates.map(([lat, lon]) =><[number,number]> [lon, lat])]
|
||||
const ids = [...this._snapOnto.nodes]
|
||||
|
||||
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.
|
||||
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
|
||||
console.warn("Skipping a node in createWay to avoid a duplicate node:", coordinate,"\nThe previous coordinates are: ", this.coordinates)
|
||||
continue
|
||||
|
|
|
@ -186,7 +186,7 @@ export default class CreateWayWithPointReuseAction extends OsmCreateAction {
|
|||
}
|
||||
|
||||
public async CreateChangeDescriptions(changes: Changes): Promise<ChangeDescription[]> {
|
||||
const theme = this._state.layoutToUse.id
|
||||
const theme = this._state?.layoutToUse?.id
|
||||
const allChanges: ChangeDescription[] = []
|
||||
const nodeIdsToUse: { lat: number, lon: number, nodeId?: number }[] = []
|
||||
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 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))
|
||||
|
||||
// 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.
|
||||
* 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][];
|
||||
/**
|
||||
|
@ -540,8 +541,6 @@ export default class ReplaceGeometryAction extends OsmChangeAction {
|
|||
id: nodeId,
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
private static createChangesetFor(csId: string,
|
||||
allChanges: {
|
||||
static createChangesetFor(csId: string,
|
||||
allChanges: {
|
||||
modifiedObjects: OsmObject[],
|
||||
newObjects: OsmObject[],
|
||||
deletedObjects: OsmObject[]
|
||||
|
|
|
@ -207,27 +207,36 @@ export abstract class OsmObject {
|
|||
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 {
|
||||
for (const tagsKey in tags) {
|
||||
if (!tags.hasOwnProperty(tagsKey)) {
|
||||
continue
|
||||
}
|
||||
const polyGuide = OsmObject.polygonFeatures.get(tagsKey)
|
||||
const polyGuide : { values: Set<string>; blacklist: boolean } = OsmObject.polygonFeatures.get(tagsKey)
|
||||
if (polyGuide === undefined) {
|
||||
continue
|
||||
}
|
||||
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
|
||||
}
|
||||
// is the key contained?
|
||||
return polyGuide.values.has(tags[tagsKey])
|
||||
// is the key contained? Then we have a match if the value is contained
|
||||
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 }> {
|
||||
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;
|
||||
|
||||
if (polygonFeature.polygon === "all") {
|
||||
|
@ -381,7 +390,7 @@ export class OsmWay extends OsmObject {
|
|||
}
|
||||
|
||||
if (element.nodes === undefined) {
|
||||
console.log("PANIC")
|
||||
console.error("PANIC: no nodes!")
|
||||
}
|
||||
|
||||
for (const nodeId of element.nodes) {
|
||||
|
@ -417,7 +426,9 @@ export class OsmWay extends OsmObject {
|
|||
}
|
||||
|
||||
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 OsmObject.isPolygon(this.tags)
|
||||
|
|
|
@ -25,7 +25,7 @@ export default class FeaturePipelineState extends MapState {
|
|||
constructor(layoutToUse: LayoutConfig) {
|
||||
super(layoutToUse);
|
||||
|
||||
const clustering = layoutToUse.clustering
|
||||
const clustering = layoutToUse?.clustering
|
||||
this.featureAggregator = TileHierarchyAggregator.createHierarchy(this);
|
||||
const clusterCounter = this.featureAggregator
|
||||
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,
|
||||
isDisplayed: QueryParameters.GetBooleanQueryParameter("overlay-" + c.id, c.defaultState, "Wether or not the overlay " + c.id + " is shown")
|
||||
}))
|
||||
})) ?? []
|
||||
this.filteredLayers = this.InitializeFilteredLayers()
|
||||
|
||||
|
||||
|
@ -142,7 +144,7 @@ export default class MapState extends UserRelatedState {
|
|||
initialized.add(overlayToggle.config)
|
||||
}
|
||||
|
||||
for (const tileLayerSource of this.layoutToUse.tileLayerSources) {
|
||||
for (const tileLayerSource of this.layoutToUse?.tileLayerSources ?? []) {
|
||||
if (initialized.has(tileLayerSource)) {
|
||||
continue
|
||||
}
|
||||
|
@ -153,28 +155,14 @@ export default class MapState extends UserRelatedState {
|
|||
|
||||
private lockBounds() {
|
||||
const layout = this.layoutToUse;
|
||||
if (layout.lockLocation) {
|
||||
if (layout.lockLocation === true) {
|
||||
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
|
||||
)
|
||||
if (!layout?.lockLocation) {
|
||||
return;
|
||||
}
|
||||
console.warn("Locking the bounds to ", layout.lockLocation);
|
||||
this.mainMapObject.installBounds(
|
||||
new BBox(layout.lockLocation),
|
||||
this.featureSwitchIsTesting.data
|
||||
)
|
||||
}
|
||||
|
||||
private initCurrentView() {
|
||||
|
@ -364,8 +352,10 @@ export default class MapState extends UserRelatedState {
|
|||
}
|
||||
|
||||
private InitializeFilteredLayers() {
|
||||
|
||||
const layoutToUse = this.layoutToUse;
|
||||
if(layoutToUse === undefined){
|
||||
return new UIEventSource<FilteredLayer[]>([])
|
||||
}
|
||||
const flayers: FilteredLayer[] = [];
|
||||
for (const layer of layoutToUse.layers) {
|
||||
let isDisplayed: UIEventSource<boolean>
|
||||
|
|
|
@ -127,7 +127,7 @@ export default class PointRenderingConfig extends WithContextLoader {
|
|||
public GetBaseIcon(tags?: any): BaseUIElement {
|
||||
tags = tags ?? {id: "node/-1"}
|
||||
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
|
||||
if (this.label === undefined) {
|
||||
defaultPin = Svg.teardrop_with_hole_green_svg()
|
||||
|
|
|
@ -338,7 +338,8 @@ export default class TagRenderingConfig {
|
|||
|
||||
const free = this.freeform?.key
|
||||
if (free !== undefined) {
|
||||
return tags[free] !== undefined
|
||||
const value = tags[free]
|
||||
return value !== undefined && value !== ""
|
||||
}
|
||||
return false
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ export default class Histogram<T> extends VariableUiElement {
|
|||
keys.sort()
|
||||
break;
|
||||
case "name-rev":
|
||||
keys.sort().reverse()
|
||||
keys.sort().reverse(/*Copy of array, inplace reverse if fine*/)
|
||||
break;
|
||||
case "count":
|
||||
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
|
||||
if (options?.feature !== undefined && options.feature.geometry.type !== "Point") {
|
||||
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.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",
|
||||
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",
|
||||
|
@ -381,7 +381,7 @@ export class ImportWayButton extends AbstractImportButton implements AutoAction
|
|||
}, {
|
||||
name: "max_move_distance",
|
||||
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",
|
||||
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)
|
||||
const args = this.parseArgs(argument, originalFeatureTags)
|
||||
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 action = this.CreateAction(
|
||||
const action = ImportWayButton.CreateAction(
|
||||
feature,
|
||||
args,
|
||||
<FeaturePipelineState>state,
|
||||
mergeConfigs,
|
||||
coordinates
|
||||
mergeConfigs
|
||||
)
|
||||
await state.changes.applyAction(action)
|
||||
}
|
||||
|
@ -455,18 +443,8 @@ export class ImportWayButton extends AbstractImportButton implements AutoAction
|
|||
|
||||
|
||||
// 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);
|
||||
|
||||
|
||||
let action = this.CreateAction(feature, args, state, mergeConfigs, coordinates);
|
||||
|
||||
let action = ImportWayButton.CreateAction(feature, args, state, mergeConfigs);
|
||||
return this.createConfirmPanelForWay(
|
||||
state,
|
||||
args,
|
||||
|
@ -508,14 +486,12 @@ export class ImportWayButton extends AbstractImportButton implements AutoAction
|
|||
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 },
|
||||
state: FeaturePipelineState,
|
||||
mergeConfigs: any[],
|
||||
coordinates: [number, number][]) {
|
||||
|
||||
mergeConfigs: any[]) {
|
||||
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 inner = [...coors]
|
||||
inner.splice(0, 1)
|
||||
|
@ -531,7 +507,7 @@ export class ImportWayButton extends AbstractImportButton implements AutoAction
|
|||
|
||||
return new CreateWayWithPointReuseAction(
|
||||
args.newTags.data,
|
||||
coordinates,
|
||||
coors,
|
||||
state,
|
||||
mergeConfigs
|
||||
)
|
||||
|
|
|
@ -28,7 +28,6 @@ export class LoginToggle extends VariableUiElement {
|
|||
const login = new LoginButton(text, state)
|
||||
super(
|
||||
state.osmConnection.loadingStatus.map(osmConnectionState => {
|
||||
console.trace("Current osm state is ", osmConnectionState)
|
||||
if(osmConnectionState === "loading"){
|
||||
return loading
|
||||
}
|
||||
|
|
|
@ -107,7 +107,7 @@ export default class SplitRoadWizard extends Toggle {
|
|||
.filter(p => GeoOperations.distanceBetween(p[0].geometry.coordinates, coordinates) < 5)
|
||||
.map(p => p[1])
|
||||
.sort((a, b) => a - b)
|
||||
.reverse()
|
||||
.reverse(/*Copy/derived list, inplace reverse is fine*/)
|
||||
if (points.length > 0) {
|
||||
for (const point of points) {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -2,34 +2,48 @@ import T from "./TestHelper";
|
|||
import {exec} from "child_process";
|
||||
|
||||
export default class CodeQualitySpec extends T {
|
||||
|
||||
constructor() {
|
||||
super([
|
||||
[
|
||||
"no constructor.name in compiled code", () => {
|
||||
|
||||
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) => {
|
||||
if (error?.message?.startsWith("Command failed: grep")) {
|
||||
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."
|
||||
}
|
||||
|
||||
}))
|
||||
|
||||
}
|
||||
]
|
||||
CodeQualitySpec.detectInCode("constructor\\.name", "This is not allowed, as minification does erase names.")
|
||||
}],
|
||||
[
|
||||
"no reverse in compiled code", () => {
|
||||
CodeQualitySpec.detectInCode("reverse()", "Reverse is stateful and changes the source list. This often causes subtle bugs")
|
||||
}]
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @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 CreateCacheSpec from "./CreateCache.spec";
|
||||
import CodeQualitySpec from "./CodeQuality.spec";
|
||||
import ImportMultiPolygonSpec from "./ImportMultiPolygon.spec";
|
||||
|
||||
|
||||
async function main() {
|
||||
|
@ -43,7 +44,8 @@ async function main() {
|
|||
new CreateNoteImportLayerSpec(),
|
||||
new ValidatedTextFieldTranslationsSpec(),
|
||||
new CreateCacheSpec(),
|
||||
new CodeQualitySpec()
|
||||
new CodeQualitySpec(),
|
||||
new ImportMultiPolygonSpec()
|
||||
]
|
||||
ScriptUtils.fixUtils();
|
||||
const realDownloadFunc = Utils.externalDownloadFunction;
|
||||
|
|
Loading…
Reference in a new issue