forked from MapComplete/MapComplete
Fix multiple bugs after user testing
This commit is contained in:
parent
bcdbf6a2dd
commit
9bd37d9cde
20 changed files with 1529 additions and 77 deletions
|
@ -1,7 +1,6 @@
|
|||
import {Basemap} from "./Basemap";
|
||||
import {TagsFilter, TagUtils} from "./TagsFilter";
|
||||
import {UIEventSource} from "../UI/UIEventSource";
|
||||
import {UIElement} from "../UI/UIElement";
|
||||
import {ElementStorage} from "./ElementStorage";
|
||||
import {Changes} from "./Changes";
|
||||
import L from "leaflet"
|
||||
|
@ -22,8 +21,7 @@ export class FilteredLayer {
|
|||
public readonly filters: TagsFilter;
|
||||
|
||||
private readonly _map: Basemap;
|
||||
private readonly _removeContainedElements;
|
||||
private readonly _removeTouchingElements;
|
||||
private readonly _maxAllowedOverlap: number;
|
||||
|
||||
private readonly _style: (properties) => any;
|
||||
|
||||
|
@ -46,8 +44,7 @@ export class FilteredLayer {
|
|||
map: Basemap, storage: ElementStorage,
|
||||
changes: Changes,
|
||||
filters: TagsFilter,
|
||||
removeContainedElements: boolean,
|
||||
removeTouchingElements: boolean,
|
||||
maxAllowedOverlap: number,
|
||||
style: ((properties) => any),
|
||||
selectedElement: UIEventSource<any>) {
|
||||
this._selectedElement = selectedElement;
|
||||
|
@ -62,8 +59,7 @@ export class FilteredLayer {
|
|||
this.filters = filters;
|
||||
this._style = style;
|
||||
this._storage = storage;
|
||||
this._removeContainedElements = removeContainedElements;
|
||||
this._removeTouchingElements = removeTouchingElements;
|
||||
this._maxAllowedOverlap = maxAllowedOverlap;
|
||||
}
|
||||
|
||||
|
||||
|
@ -92,8 +88,8 @@ export class FilteredLayer {
|
|||
|
||||
const notShadowed = [];
|
||||
for (const feature of leftoverFeatures) {
|
||||
if (this._removeContainedElements || this._removeTouchingElements) {
|
||||
if (GeoOperations.featureIsContainedInAny(feature, selfFeatures, this._removeTouchingElements)) {
|
||||
if (this._maxAllowedOverlap !== undefined && this._maxAllowedOverlap >= 0) {
|
||||
if (GeoOperations.featureIsContainedInAny(feature, selfFeatures, this._maxAllowedOverlap)) {
|
||||
// This feature is filtered away
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ import {Basemap} from "./Basemap";
|
|||
import {UIEventSource} from "../UI/UIEventSource";
|
||||
import {UIElement} from "../UI/UIElement";
|
||||
import L from "leaflet";
|
||||
import {Helpers} from "../Helpers";
|
||||
|
||||
export class GeoLocationHandler extends UIElement {
|
||||
|
||||
|
@ -11,7 +12,7 @@ export class GeoLocationHandler extends UIElement {
|
|||
}> = new UIEventSource<{ latlng: number, accuracy: number }>(undefined);
|
||||
|
||||
private _isActive: UIEventSource<boolean> = new UIEventSource<boolean>(false);
|
||||
|
||||
private _permission: UIEventSource<string> = new UIEventSource<string>("");
|
||||
private _map: Basemap;
|
||||
private _marker: any;
|
||||
|
||||
|
@ -20,6 +21,7 @@ export class GeoLocationHandler extends UIElement {
|
|||
this._map = map;
|
||||
this.ListenTo(this.currentLocation);
|
||||
this.ListenTo(this._isActive);
|
||||
this.ListenTo(this._permission);
|
||||
|
||||
const self = this;
|
||||
|
||||
|
@ -27,6 +29,7 @@ export class GeoLocationHandler extends UIElement {
|
|||
function onAccuratePositionProgress(e) {
|
||||
console.log(e.accuracy);
|
||||
console.log(e.latlng);
|
||||
self.currentLocation.setData({latlng: e.latlng, accuracy: e.accuracy});
|
||||
}
|
||||
|
||||
function onAccuratePositionFound(e) {
|
||||
|
@ -62,8 +65,19 @@ export class GeoLocationHandler extends UIElement {
|
|||
self._marker = newMarker;
|
||||
});
|
||||
|
||||
navigator.permissions.query({ name: 'geolocation' })
|
||||
.then(function(){self.StartGeolocating()});
|
||||
navigator.permissions.query({name: 'geolocation'})
|
||||
.then(function (status) {
|
||||
console.log("Geolocation is already", status)
|
||||
if (status.state === "granted") {
|
||||
self.StartGeolocating();
|
||||
}
|
||||
self._permission.setData(status.state);
|
||||
status.onchange = function () {
|
||||
self._permission.setData(status.state);
|
||||
}
|
||||
});
|
||||
|
||||
this.HideOnEmpty(true);
|
||||
|
||||
}
|
||||
|
||||
|
@ -79,20 +93,33 @@ export class GeoLocationHandler extends UIElement {
|
|||
}
|
||||
|
||||
|
||||
private StartGeolocating(){
|
||||
private StartGeolocating() {
|
||||
const self = this;
|
||||
|
||||
if (self._permission.data === "denied") {
|
||||
return "";
|
||||
}
|
||||
if (self.currentLocation.data !== undefined) {
|
||||
self._map.map.flyTo(self.currentLocation.data.latlng, 18);
|
||||
return;
|
||||
}
|
||||
|
||||
self._isActive.setData(true);
|
||||
|
||||
console.log("Searching location using GPS")
|
||||
self._map.map.findAccuratePosition({
|
||||
maxWait: 15000, // defaults to 10000
|
||||
desiredAccuracy: 30 // defaults to 20
|
||||
maxWait: 10000, // defaults to 10000
|
||||
desiredAccuracy: 50 // defaults to 20
|
||||
});
|
||||
|
||||
|
||||
if (!self._isActive.data) {
|
||||
self._isActive.setData(true);
|
||||
Helpers.DoEvery(60000, () => {
|
||||
self._map.map.findAccuratePosition({
|
||||
maxWait: 10000, // defaults to 10000
|
||||
desiredAccuracy: 50 // defaults to 20
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
InnerUpdate(htmlElement: HTMLElement) {
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
import * as turf from 'turf'
|
||||
|
||||
export class GeoOperations {
|
||||
|
||||
static surfaceAreaInSqMeters(feature: any) {
|
||||
return turf.area(feature);
|
||||
}
|
||||
|
||||
static featureIsContainedInAny(feature: any, shouldNotContain: any[], noTouching: boolean = false): boolean {
|
||||
|
||||
static featureIsContainedInAny(feature: any,
|
||||
shouldNotContain: any[],
|
||||
maxOverlapPercentage: number): boolean {
|
||||
// Returns 'false' if no problematic intersection is found
|
||||
if (feature.geometry.type === "Point") {
|
||||
const coor = feature.geometry.coordinates;
|
||||
for (const shouldNotContainElement of shouldNotContain) {
|
||||
|
@ -21,38 +28,55 @@ export class GeoOperations {
|
|||
}
|
||||
|
||||
|
||||
if (feature.geometry.type === "Polygon") {
|
||||
if (feature.geometry.type === "Polygon" || feature.geometry.type === "MultiPolygon") {
|
||||
|
||||
const poly = feature;
|
||||
let featureBBox = BBox.get(feature);
|
||||
const featureSurface = GeoOperations.surfaceAreaInSqMeters(poly);
|
||||
for (const shouldNotContainElement of shouldNotContain) {
|
||||
|
||||
let shouldNotContainBBox = BBox.get(shouldNotContainElement);
|
||||
let featureBBox = BBox.get(feature);
|
||||
if (!featureBBox.overlapsWith(shouldNotContainBBox)) {
|
||||
const shouldNotContainBBox = BBox.get(shouldNotContainElement);
|
||||
const overlaps = featureBBox.overlapsWith(shouldNotContainBBox)
|
||||
if (!overlaps) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (noTouching) {
|
||||
if (GeoOperations.isPolygonTouching(poly, shouldNotContainElement)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
if (GeoOperations.isPolygonInside(poly, shouldNotContainElement)) {
|
||||
// Calculate the surface area of the intersection
|
||||
// If it is too big, refuse
|
||||
try {
|
||||
|
||||
const intersection = turf.intersect(poly, shouldNotContainElement);
|
||||
if (intersection == null) {
|
||||
continue;
|
||||
}
|
||||
const intersectionSize = turf.area(intersection);
|
||||
const ratio = intersectionSize / featureSurface;
|
||||
console.log("Intersection ratio", ratio, "intersection:", intersectionSize, "featuresize:", featureSurface, "targetRatio", maxOverlapPercentage / 100);
|
||||
|
||||
if (ratio * 100 >= maxOverlapPercentage) {
|
||||
console.log("Refused", poly.id, " due to ", shouldNotContainElement.id, "intersection ratio is ", ratio, "which is bigger then the target ratio of ", (maxOverlapPercentage / 100))
|
||||
return true;
|
||||
}
|
||||
} catch (exception) {
|
||||
console.log("EXCEPTION CAUGHT WHILE INTERSECTING: ", exception);
|
||||
// We assume that this failed due to an intersection
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
return false; // No problematic intersections found
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simple check: that every point of the polygon is inside the container
|
||||
* @param polygon
|
||||
* @param container
|
||||
*/
|
||||
static isPolygonInside(polygon, container) {
|
||||
private static isPolygonInside(polygon, container) {
|
||||
for (const coor of polygon.geometry.coordinates[0]) {
|
||||
if (!GeoOperations.inside(coor, container)) {
|
||||
return false;
|
||||
|
@ -66,7 +90,7 @@ export class GeoOperations {
|
|||
* @param polygon
|
||||
* @param container
|
||||
*/
|
||||
static isPolygonTouching(polygon, container) {
|
||||
private static isPolygonTouching(polygon, container) {
|
||||
for (const coor of polygon.geometry.coordinates[0]) {
|
||||
if (GeoOperations.inside(coor, container)) {
|
||||
return true;
|
||||
|
@ -76,7 +100,7 @@ export class GeoOperations {
|
|||
}
|
||||
|
||||
|
||||
static inside(pointCoordinate, feature): boolean {
|
||||
private static inside(pointCoordinate, feature): boolean {
|
||||
// ray-casting algorithm based on
|
||||
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
|
||||
|
||||
|
@ -134,10 +158,19 @@ class BBox {
|
|||
this.minLon = Math.min(this.minLon, coordinate[0]);
|
||||
this.minLat = Math.min(this.minLat, coordinate[1]);
|
||||
}
|
||||
this.check();
|
||||
}
|
||||
|
||||
private check() {
|
||||
if (isNaN(this.maxLon) || isNaN(this.maxLat) || isNaN(this.minLon) || isNaN(this.minLat)) {
|
||||
console.log(this);
|
||||
throw "BBOX has NAN";
|
||||
}
|
||||
}
|
||||
|
||||
public overlapsWith(other: BBox) {
|
||||
|
||||
this.check();
|
||||
other.check();
|
||||
if (this.maxLon < other.minLon) {
|
||||
return false;
|
||||
}
|
||||
|
@ -155,13 +188,22 @@ class BBox {
|
|||
|
||||
static get(feature) {
|
||||
if (feature.bbox === undefined) {
|
||||
if (feature.geometry.type === "Polygon") {
|
||||
|
||||
if (feature.geometry.type === "MultiPolygon") {
|
||||
let coordinates = [];
|
||||
for (const coorlist of feature.geometry.coordinates) {
|
||||
coordinates = coordinates.concat(coorlist[0]);
|
||||
}
|
||||
feature.bbox = new BBox(coordinates);
|
||||
} else if (feature.geometry.type === "Polygon") {
|
||||
feature.bbox = new BBox(feature.geometry.coordinates[0]);
|
||||
} else if (feature.geometry.type === "LineString") {
|
||||
feature.bbox = new BBox(feature.geometry.coordinates);
|
||||
} else {
|
||||
} else if (feature.geometry.type === "Point") {
|
||||
// Point
|
||||
feature.bbox = new BBox([feature.geometry.coordinates]);
|
||||
} else {
|
||||
throw "Cannot calculate bbox, unknown type " + feature.geometry.type;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -76,7 +76,14 @@ export class OsmConnection {
|
|||
console.log(userInfo);
|
||||
data.name = userInfo.getAttribute('display_name');
|
||||
data.csCount = userInfo.getElementsByTagName("changesets")[0].getAttribute("count");
|
||||
data.img = userInfo.getElementsByTagName("img")[0].getAttribute("href");
|
||||
|
||||
data.img = undefined;
|
||||
const imgEl = userInfo.getElementsByTagName("img");
|
||||
if (imgEl !== undefined && imgEl[0] !== undefined) {
|
||||
data.img = imgEl[0].getAttribute("href");
|
||||
}
|
||||
data.img = data.img ?? "./assets/osm-logo.svg";
|
||||
|
||||
const messages = userInfo.getElementsByTagName("messages")[0].getElementsByTagName("received")[0];
|
||||
data.unreadMessages = parseInt(messages.getAttribute("unread"));
|
||||
data.totalMessages = parseInt(messages.getAttribute("count"));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue