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
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue