Cleaning filtered layer

This commit is contained in:
Pieter Vander Vennet 2020-11-16 01:59:30 +01:00
parent 8e5e249e6b
commit 314894085a
12 changed files with 232 additions and 280 deletions

View file

@ -116,4 +116,63 @@ export default class LayerConfig {
}
public GenerateLeafletStyle(tags: any):
{
color: string;
icon: { popupAnchor: [number, number]; iconAnchor: [number, number]; iconSize: [number, number]; iconUrl: string }; weight: number; dashArray: number[]
} {
const iconUrl = this.icon?.GetRenderValue(tags)?.txt;
const iconSize = (this.iconSize?.GetRenderValue(tags)?.txt ?? "40,40,center").split(",");
const dashArray = this.dashArray.GetRenderValue(tags)?.txt.split(" ").map(Number);
function num(str, deflt = 40) {
const n = Number(str);
if (isNaN(n)) {
return deflt;
}
return n;
}
const iconW = num(iconSize[0]);
const iconH = num(iconSize[1]);
const mode = iconSize[2] ?? "center"
let anchorW = iconW / 2;
let anchorH = iconH / 2;
if (mode === "left") {
anchorW = 0;
}
if (mode === "right") {
anchorW = iconW;
}
if (mode === "top") {
anchorH = 0;
}
if (mode === "bottom") {
anchorH = iconH;
}
const color = this.color?.GetRenderValue(tags)?.txt ?? "#00f";
let weight = num(this.width?.GetRenderValue(tags)?.txt, 5);
return {
icon:
{
iconUrl: iconUrl,
iconSize: [iconW, iconH],
iconAnchor: [anchorW, anchorH],
popupAnchor: [0, 3 - anchorH]
},
color: color,
weight: weight,
dashArray: dashArray
};
}
}

View file

@ -475,18 +475,16 @@ export class InitUiElements {
throw "Layer " + layer + " was not substituted";
}
const generateInfo = (tagsES) => {
return new FeatureInfoBox(
tagsES,
layer,
)
};
const flayer: FilteredLayer = FilteredLayer.fromDefinition(layer, generateInfo);
const flayer: FilteredLayer = new FilteredLayer(layer,
(tagsES) => {
return new FeatureInfoBox(
tagsES,
layer,
)
});
flayers.push(flayer);
QueryParameters.GetQueryParameter("layer-" + layer.id, "true", "Wehter or not layer "+layer.id+" is shown")
QueryParameters.GetQueryParameter("layer-" + layer.id, "true", "Wehter or not layer " + layer.id + " is shown")
.map<boolean>((str) => str !== "false", [], (b) => b.toString())
.syncWith(
flayer.isDisplayed

View file

@ -25,13 +25,9 @@ export class FilteredLayer {
public readonly layerDef: LayerConfig;
private readonly _maxAllowedOverlap: number;
private readonly _style: (properties) => { color: string, weight?: number, icon: { iconUrl: string, iconSize?: [number, number], popupAnchor?: [number, number], iconAnchor?: [number, number] } };
/** The featurecollection from overpass
*/
private _dataFromOverpass: any[];
private readonly _wayHandling: number;
/** List of new elements, geojson features
*/
private _newElements = [];
@ -49,60 +45,7 @@ export class FilteredLayer {
) {
this.layerDef = layerDef;
this._wayHandling = layerDef.wayHandling;
this._showOnPopup = showOnPopup;
this._style = (tags) => {
const iconUrl = layerDef.icon?.GetRenderValue(tags)?.txt;
const iconSize = (layerDef.iconSize?.GetRenderValue(tags)?.txt ?? "40,40,center").split(",");
const dashArray = layerDef.dashArray.GetRenderValue(tags)?.txt.split(" ").map(Number);
function num(str, deflt = 40) {
const n = Number(str);
if (isNaN(n)) {
return deflt;
}
return n;
}
const iconW = num(iconSize[0]);
const iconH = num(iconSize[1]);
const mode = iconSize[2] ?? "center"
let anchorW = iconW / 2;
let anchorH = iconH / 2;
if (mode === "left") {
anchorW = 0;
}
if (mode === "right") {
anchorW = iconW;
}
if (mode === "top") {
anchorH = 0;
}
if (mode === "bottom") {
anchorH = iconH;
}
const color = layerDef.color?.GetRenderValue(tags)?.txt ?? "#00f";
let weight = num(layerDef.width?.GetRenderValue(tags)?.txt, 5);
return {
icon:
{
iconUrl: iconUrl,
iconSize: [iconW, iconH],
iconAnchor: [anchorW, anchorH],
popupAnchor: [0, 3 - anchorH]
},
color: color,
weight: weight,
dashArray: dashArray
};
};
this.name = name;
this.filters = layerDef.overpassTags;
this._maxAllowedOverlap = layerDef.hideUnderlayingFeaturesMinPercentage;
@ -123,13 +66,6 @@ export class FilteredLayer {
}
})
}
static fromDefinition(definition, showOnPopup: (tags: UIEventSource<any>, feature: any) => UIElement): FilteredLayer {
return new FilteredLayer(definition, showOnPopup);
}
/**
* The main function to load data into this layer.
* The data that is NOT used by this layer, is returned as a geojson object; the other data is rendered
@ -138,32 +74,15 @@ export class FilteredLayer {
const leftoverFeatures = [];
const selfFeatures = [];
for (let feature of geojson.features) {
// feature.properties contains all the properties
const tags = TagUtils.proprtiesToKV(feature.properties);
if (!this.filters.matches(tags)) {
leftoverFeatures.push(feature);
continue;
}
if (feature.geometry.type !== "Point") {
const centerPoint = GeoOperations.centerpoint(feature);
if (this._wayHandling === LayerConfig.WAYHANDLING_CENTER_AND_WAY) {
selfFeatures.push(centerPoint);
} else if (this._wayHandling === LayerConfig.WAYHANDLING_CENTER_ONLY) {
feature = centerPoint;
}
}
selfFeatures.push(feature);
}
this.RenderLayer({
type: "FeatureCollection",
features: selfFeatures
})
this.RenderLayer(selfFeatures)
const notShadowed = [];
for (const feature of leftoverFeatures) {
@ -186,18 +105,140 @@ export class FilteredLayer {
public AddNewElement(element) {
this._newElements.push(element);
this.RenderLayer({features: this._dataFromOverpass}, element); // Update the layer
this.RenderLayer( this._dataFromOverpass); // Update the layer
}
private RenderLayer(data, openPopupOf = undefined) {
let self = this;
private RenderLayer(features) {
if (this._geolayer !== undefined && this._geolayer !== null) {
// Remove the old geojson layer from the map - we'll reshow all the elements later on anyway
State.state.bm.map.removeLayer(this._geolayer);
}
// We fetch all the data we have to show:
let fusedFeatures = this.ApplyWayHandling(this.FuseData(features));
console.log("Fused:",fusedFeatures)
// And we copy some features as points - if needed
const data = {
type: "FeatureCollection",
features: fusedFeatures
}
let self = this;
console.log(data);
this._geolayer = L.geoJSON(data, {
/* style: feature => {
self.layerDef.GenerateLeafletStyle(feature.properties);
return {
color: "#f00",
weight: 4
}
},*/
/*
pointToLayer: function (feature, latLng) {
// Point to layer converts the 'point' to a layer object - as the geojson layer natively cannot handle points
// Click handling is done in the next step
const style = self.layerDef.GenerateLeafletStyle(feature.properties);
let marker;
if (style.icon === undefined) {
marker = L.circle(latLng, {
radius: 25,
color: style.color
});
} else if (style.icon.iconUrl.startsWith("$circle")) {
marker = L.circle(latLng, {
radius: 25,
color: style.color
});
} else {
if (style.icon.iconSize === undefined) {
style.icon.iconSize = [50, 50]
}
marker = L.marker(latLng, {
icon: L.icon(style.icon)
});
}
return marker;
},*/
/*
onEachFeature: function (feature, layer:Layer) {
layer.on("click", (e) => {
if (layer.getPopup() === undefined
&& (window.screen.availHeight > 600 || window.screen.availWidth > 600) // We DON'T trigger this code on small screens! No need to create a popup
) {
const popup = L.popup({
autoPan: true,
closeOnEscapeKey: true,
}, layer);
// @ts-ignore
popup.setLatLng(e.latlng)
layer.bindPopup(popup);
const eventSource = State.state.allElements.addOrGetElement(feature);
const uiElement = self._showOnPopup(eventSource, feature);
// We first render the UIelement (which'll still need an update later on...)
// But at least it'll be visible already
popup.setContent(uiElement.Render());
popup.openOn(State.state.bm.map);
// popup.openOn(State.state.bm.map);
// ANd we perform the pending update
uiElement.Update();
}
// We set the element as selected...
State.state.selectedElement.setData(feature);
// We mark the event as consumed
L.DomEvent.stop(e);
});
}
*/
}
)
;
if (this.combinedIsDisplayed.data) {
this._geolayer.addTo(State.state.bm.map);
}
}
private ApplyWayHandling(fusedFeatures: any[]) {
if (this.layerDef.wayHandling === LayerConfig.WAYHANDLING_DEFAULT) {
// We don't have to do anything special
return fusedFeatures;
}
// We have to convert all the ways into centerpoints
const existingPoints = [];
const newPoints = [];
const existingWays = [];
for (const feature of fusedFeatures) {
if (feature.geometry.type === "Point") {
existingPoints.push(feature);
continue;
}
existingWays.push(feature);
const centerPoint = GeoOperations.centerpoint(feature);
newPoints.push(centerPoint);
}
fusedFeatures = existingPoints.concat(newPoints);
if (this.layerDef.wayHandling === LayerConfig.WAYHANDLING_CENTER_AND_WAY) {
fusedFeatures = fusedFeatures.concat(existingWays)
}
return fusedFeatures;
}
//*Fuses the old and the new datasets*/
private FuseData(data: any[]) {
const oldData = this._dataFromOverpass ?? [];
// We keep track of all the ids that are freshly loaded in order to avoid adding duplicates
@ -205,7 +246,7 @@ export class FilteredLayer {
// A list of all the features to show
const fusedFeatures = [];
// First, we add all the fresh data:
for (const feature of data.features) {
for (const feature of data) {
idsFromOverpass.add(feature.properties.id);
fusedFeatures.push(feature);
}
@ -226,133 +267,6 @@ export class FilteredLayer {
fusedFeatures.push(feature);
}
}
// We use a new, fused dataset
data = {
type: "FeatureCollection",
features: fusedFeatures
}
// The data is split in two parts: the point and the rest
// The points get a special treatment in order to render them properly
// Note that some features might get a point representation as well
const runWhenAdded: (() => void)[] = []
this._geolayer = L.geoJSON(data, {
style: function (feature) {
return self._style(feature.properties);
},
pointToLayer: function (feature, latLng) {
const style = self._style(feature.properties);
let marker;
if (style.icon === undefined) {
marker = L.circle(latLng, {
radius: 25,
color: style.color
});
} else if (style.icon.iconUrl.startsWith("$circle")) {
marker = L.circle(latLng, {
radius: 25,
color: style.color
});
} else {
if (style.icon.iconSize === undefined) {
style.icon.iconSize = [50, 50]
}
// @ts-ignore
marker = L.marker(latLng, {
icon: L.icon(style.icon),
});
}
let eventSource = State.state.allElements.addOrGetElement(feature);
const popup = L.popup({}, marker);
let uiElement: UIElement;
let content = undefined;
let p = marker.bindPopup(popup)
.on("popupopen", () => {
if (content === undefined) {
uiElement = self._showOnPopup(eventSource, feature);
// Lazily create the content
content = uiElement.Render();
}
popup.setContent(content);
uiElement.Update();
});
if (feature === openPopupOf) {
runWhenAdded.push(() => {
p.openPopup();
})
}
return marker;
},
onEachFeature: function (feature, layer:Layer) {
// We monky-patch the feature element with an update-style
function updateStyle () {
// @ts-ignore
if (layer.setIcon) {
const style = self._style(feature.properties);
const icon = style.icon;
if (icon.iconUrl) {
if (icon.iconUrl.startsWith("$circle")) {
// pass
} else {
// @ts-ignore
layer.setIcon(L.icon(icon))
}
}
} else {
self._geolayer.setStyle(function (featureX) {
return self._style(featureX.properties);
});
}
}
let eventSource = State.state.allElements.addOrGetElement(feature);
eventSource.addCallback(updateStyle);
function openPopup(e) {
if (feature.geometry.type === "Point") {
return; // Points bind their own popups
}
const uiElement = self._showOnPopup(eventSource, feature);
L.popup({
autoPan: true,
}).setContent(uiElement.Render())
.setLatLng(e.latlng)
.openOn(State.state.bm.map);
uiElement.Update();
if (e) {
L.DomEvent.stop(e); // Marks the event as consumed
}
}
layer.on("click", (e) => {
updateStyle();
openPopup(e);
State.state.selectedElement.setData(feature);
});
}
});
if (this.combinedIsDisplayed.data) {
this._geolayer.addTo(State.state.bm.map);
for (const f of runWhenAdded) {
f();
}
}
return fusedFeatures;
}
}

View file

@ -8,34 +8,13 @@ import Combine from "./Base/Combine";
*/
export class FullScreenMessageBox extends UIElement {
private static readonly _toTheMap_height : string = "5em";
private readonly returnToTheMap: UIElement;
private _content: UIElement;
constructor(onClear: (() => void)) {
super();
super(State.state.fullScreenMessage);
this.HideOnEmpty(true);
const self = this;
State.state.fullScreenMessage.addCallbackAndRun(uiElement => {
self.Update();
if (uiElement === undefined) {
location.hash = "";
} else {
// The 'hash' makes sure a new piece of history is added. This makes the 'back-button' on android remove the popup
location.hash = "#element";
}
});
if (window !== undefined) {
window.onhashchange = function () {
if (location.hash === "") {
// No more element: back to the map!
State.state.fullScreenMessage.setData(undefined);
onClear();
}
}
}
this.returnToTheMap =
new Combine([Translations.t.general.returnToTheMap.Clone().SetStyle("font-size:xx-large")])
@ -44,7 +23,7 @@ export class FullScreenMessageBox extends UIElement {
"z-index: 10000;" +
"bottom: 0;" +
"left: 0;" +
`height: ${FullScreenMessageBox._toTheMap_height};` +
`height: var(--return-to-the-map-height);` +
"width: 100vw;" +
"color: var(--catch-detail-color-contrast);" +
"font-weight: bold;" +
@ -55,10 +34,8 @@ export class FullScreenMessageBox extends UIElement {
"padding-bottom: 1.2em;" +
"box-sizing:border-box")
.onClick(() => {
console.log("Returning...")
State.state.fullScreenMessage.setData(undefined);
onClear();
self.Update();
});
}
@ -73,9 +50,9 @@ export class FullScreenMessageBox extends UIElement {
"display:block;" +
"padding: 1em;" +
"padding-bottom:6em;" +
`margin-bottom:${FullScreenMessageBox._toTheMap_height};` +
`margin-bottom: var(--return-to-the-map-height);` +
"box-sizing:border-box;" +
`height:calc(100vh - ${FullScreenMessageBox._toTheMap_height});` +
`height:calc(100vh - var(--return-to-the-map-height));` +
"overflow-y: auto;" +
"max-width:100vw;" +
"overflow-x:hidden;" +

View file

@ -68,7 +68,6 @@ export default class DirectionInput extends InputElement<string> {
htmlElement.ontouchstart = (ev: TouchEvent) => {
onPosChange(ev.touches[0].clientX, ev.touches[0].clientY);
ev.preventDefault();
}
let isDown = false;

View file

@ -27,24 +27,25 @@ export default class EditableTagRendering extends UIElement {
this.ListenTo(this._editMode);
this.ListenTo(State.state?.osmConnection?.userDetails)
const self = this;
this._answer = new TagRenderingAnswer(tags, configuration);
this._answer.SetStyle("width:100%;")
if (this._configuration.question !== undefined) {
// 2.3em total width
if(State.state.featureSwitchUserbadge.data){
this._editButton =
Svg.pencil_svg().SetClass("edit-button")
.onClick(() => {
self._editMode.setData(true);
});
if (State.state.featureSwitchUserbadge.data) {
// 2.3em total width
const self = this;
this._editButton =
Svg.pencil_svg().SetClass("edit-button")
.onClick(() => {
self._editMode.setData(true);
});
}
}
}
private GenerateQuestion() {
const self = this;
if (this._configuration.question !== undefined) {
// And at last, set up the skip button
const cancelbutton =
Translations.t.general.cancel.Clone()
@ -53,7 +54,7 @@ export default class EditableTagRendering extends UIElement {
self._editMode.setData(false)
});
this._question = new TagRenderingQuestion(tags, configuration,
return new TagRenderingQuestion(this._tags, this._configuration,
() => {
self._editMode.setData(false)
},
@ -65,6 +66,7 @@ export default class EditableTagRendering extends UIElement {
InnerRender(): string {
if (this._editMode.data) {
this._question = this.GenerateQuestion();
return this._question.Render();
}

View file

@ -118,7 +118,6 @@ export default class TagRenderingQuestion extends UIElement {
const inputEl = new InputElementMap<number[], TagsFilter>(
checkBoxes,
(t0, t1) => {
console.log("IsEquiv?",t0, t1, t0?.isEquivalent(t1))
return t0?.isEquivalent(t1) ?? false
},
(indices) => {
@ -162,8 +161,6 @@ export default class TagRenderingQuestion extends UIElement {
}
}
console.log(indices, freeformExtras);
if (freeformField) {
if (freeformExtras.length > 0) {
freeformField.GetValue().setData(new Tag(this._configuration.freeform.key, freeformExtras.join(";")));

View file

@ -8,7 +8,6 @@ import Locale from "./i18n/Locale";
import State from "../State";
import {UIEventSource} from "../Logic/UIEventSource";
import {Img} from "./Img";
import Svg from "../Svg";
/**
@ -115,8 +114,8 @@ export class SimpleAddUI extends UIElement {
const loc = State.state.bm.LastClickLocation.data;
let feature = State.state.changes.createElement(tags, loc.lat, loc.lon);
layerToAddTo.AddNewElement(feature);
State.state.selectedElement.setData(feature);
layerToAddTo.AddNewElement(feature);
}
}

View file

@ -40,27 +40,27 @@
id="namedview8"
showgrid="false"
inkscape:zoom="1.5733334"
inkscape:cx="84.526201"
inkscape:cx="-20.982269"
inkscape:cy="188.2981"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg6" />
<path
style="fill:#ffffff;stroke:#ffffff;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
d="M 34.759205,76.188657 H 262.93718 c 37.50409,0.111509 38.67272,47.104633 0,46.615173 H 34.759205 c -33.95631907,0.10039 -33.5365026,-46.998068 0,-46.615173 z"
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 34.759205,66.019166 H 262.93718 c 37.50409,0.111509 38.67272,47.104634 0,46.615174 H 34.759205 c -33.95631907,0.10039 -33.5365026,-46.998069 0,-46.615174 z"
id="path818"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
<path
style="fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:15;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 49.750453,132.33688 249.19481,132.00969 C 248.9489,264.76342 49.140118,261.25015 49.750453,132.33688 Z"
d="M 49.750453,122.23061 249.19481,121.8402 C 248.9489,280.24546 49.140118,276.05333 49.750453,122.23061 Z"
id="path823"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccc" />
<g
id="g848"
transform="matrix(1.4204816,0,0,1.4204816,-61.951413,-43.532931)"
transform="matrix(1.4204816,0,0,1.4204816,-61.951413,-40.990558)"
style="stroke-width:0.70398653">
<ellipse
ry="23.81991"

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -43,10 +43,15 @@ Contains tweaks for small screens
#geolocate-button {
display: block;
}
.leaflet-popup {
/* Popups are hidden on mobile */
transform: unset !important;
}
.leaflet-popup-content {
/* On mobile, the popups are shown as a full-screen element */
display: none;
visibility: hidden;
}
#centermessage {

View file

@ -7,7 +7,9 @@
--background-color: white;
--foreground-color: black;
--popup-border: white;
--shadow-color: #00000066
--shadow-color: #00000066;
--return-to-the-map-height: 5em;
}
html, body {

View file

@ -25,7 +25,7 @@
"deploy:staging": "npm run prepare-deploy && rm -rf /home/pietervdvn/git/pietervdvn.github.io/Staging/* && cp -r dist/* /home/pietervdvn/git/pietervdvn.github.io/Staging/ && cd /home/pietervdvn/git/pietervdvn.github.io/ && git add * && git commit -m 'New MapComplete Version' && git push && cd - && npm run clean",
"deploy:production": "npm run prepare-deploy && npm run optimize-images && rm -rf /home/pietervdvn/git/pietervdvn.github.io/MapComplete/* && cp -r dist/* /home/pietervdvn/git/pietervdvn.github.io/MapComplete/ && cd /home/pietervdvn/git/pietervdvn.github.io/ && git add * && git commit -m 'New MapComplete Version' && git push && cd - && npm run clean",
"clean": "rm *.webmanifest && find *.html | grep -v \"\\(index\\|land\\|test\\|preferences\\|customGenerator\\).html\" | xargs rm"
"clean": "rm -rf .cache/ && (find *.html | grep -v \"\\(index\\|land\\|test\\|preferences\\|customGenerator\\).html\" | xargs rm) && (find *.webmanifest | xargs rm)"
},
"keywords": [