forked from MapComplete/MapComplete
Merge master
This commit is contained in:
commit
89a0be8903
150 changed files with 4201 additions and 9581 deletions
|
|
@ -23,7 +23,7 @@
|
|||
bearing: number
|
||||
}> = map.location.mapD(
|
||||
({ lon, lat }) => {
|
||||
const current = currentLocation.data
|
||||
const current = currentLocation?.data
|
||||
if (!current) {
|
||||
return undefined
|
||||
}
|
||||
|
|
@ -41,6 +41,9 @@
|
|||
let compass = Orientation.singleton.alpha
|
||||
let relativeBearing: Store<{ distance: string; bearing: Translation }> = compass.mapD(
|
||||
(compass) => {
|
||||
if(!distanceToCurrentLocation.data){
|
||||
return undefined
|
||||
}
|
||||
const bearing: Translation =
|
||||
relativeDir[
|
||||
GeoOperations.bearingToHumanRelative(distanceToCurrentLocation.data.bearing - compass)
|
||||
|
|
|
|||
|
|
@ -87,6 +87,7 @@
|
|||
if (snapToLayers?.length > 0) {
|
||||
const snapSources: FeatureSource[] = []
|
||||
for (const layerId of snapToLayers ?? []) {
|
||||
// We assume that the layer contains the data, as the OSM-API-Feature-source should have loaded them, even though the layer might not be displayed
|
||||
const layer: FeatureSourceForLayer = state.perLayer.get(layerId)
|
||||
snapSources.push(layer)
|
||||
if (layer.features === undefined) {
|
||||
|
|
|
|||
|
|
@ -3,10 +3,8 @@
|
|||
import { ArrowDownTrayIcon } from "@babeard/svelte-heroicons/mini"
|
||||
import Tr from "../Base/Tr.svelte"
|
||||
import Translations from "../i18n/Translations"
|
||||
import type { FeatureCollection } from "geojson"
|
||||
import Loading from "../Base/Loading.svelte"
|
||||
import { Translation } from "../i18n/Translation"
|
||||
import DownloadHelper from "./DownloadHelper"
|
||||
import { Utils } from "../../Utils"
|
||||
import type { PriviligedLayerType } from "../../Models/Constants"
|
||||
import { UIEventSource } from "../../Logic/UIEventSource"
|
||||
|
|
@ -16,14 +14,11 @@
|
|||
export let extension: string
|
||||
export let mimetype: string
|
||||
export let construct: (
|
||||
geojsonCleaned: FeatureCollection,
|
||||
title: string,
|
||||
status?: UIEventSource<string>
|
||||
) => (Blob | string) | Promise<void>
|
||||
) => Promise<Blob | string>
|
||||
export let mainText: Translation
|
||||
export let helperText: Translation
|
||||
export let metaIsIncluded: boolean
|
||||
let downloadHelper: DownloadHelper = new DownloadHelper(state)
|
||||
|
||||
const t = Translations.t.general.download
|
||||
|
||||
|
|
@ -31,31 +26,21 @@
|
|||
let isError = false
|
||||
|
||||
let status: UIEventSource<string> = new UIEventSource<string>(undefined)
|
||||
|
||||
async function clicked() {
|
||||
isExporting = true
|
||||
|
||||
const gpsLayer = state.layerState.filteredLayers.get(<PriviligedLayerType>"gps_location")
|
||||
state.lastClickObject.features.setData([])
|
||||
state.userRelatedState.preferencesAsTags.data["__showTimeSensitiveIcons"] = "no"
|
||||
state.userRelatedState.preferencesAsTags.ping()
|
||||
const gpsIsDisplayed = gpsLayer.isDisplayed.data
|
||||
try {
|
||||
gpsLayer.isDisplayed.setData(false)
|
||||
const geojson: FeatureCollection = downloadHelper.getCleanGeoJson(metaIsIncluded)
|
||||
const name = state.layout.id
|
||||
|
||||
const title = `MapComplete_${name}_export_${new Date()
|
||||
.toISOString()
|
||||
.substr(0, 19)}.${extension}`
|
||||
const promise = construct(geojson, title, status)
|
||||
let data: Blob | string
|
||||
if (typeof promise === "string") {
|
||||
data = promise
|
||||
} else if (typeof promise["then"] === "function") {
|
||||
data = await (<Promise<Blob | string>>promise)
|
||||
} else {
|
||||
data = <Blob>promise
|
||||
}
|
||||
const data: Blob | string = await construct(title, status)
|
||||
if (!data) {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ export default class DownloadHelper {
|
|||
return header + "\n" + elements.join("\n") + "\n</svg>"
|
||||
}
|
||||
|
||||
public getCleanGeoJsonPerLayer(includeMetaData: boolean): Map<string, Feature[]> {
|
||||
private getCleanGeoJsonPerLayer(includeMetaData: boolean): Map<string, Feature[]> {
|
||||
const state = this._state
|
||||
const featuresPerLayer = new Map<string, any[]>()
|
||||
const neededLayers = state.layout.layers.filter((l) => l.source !== null).map((l) => l.id)
|
||||
|
|
@ -161,6 +161,7 @@ export default class DownloadHelper {
|
|||
|
||||
for (const neededLayer of neededLayers) {
|
||||
const indexedFeatureSource = state.perLayer.get(neededLayer)
|
||||
|
||||
let features = indexedFeatureSource.GetFeaturesWithin(bbox)
|
||||
// The 'indexedFeatureSources' contains _all_ features, they are not filtered yet
|
||||
const filter = state.layerState.filteredLayers.get(neededLayer)
|
||||
|
|
|
|||
|
|
@ -17,9 +17,16 @@
|
|||
const downloadHelper = new DownloadHelper(state)
|
||||
|
||||
let metaIsIncluded = false
|
||||
const name = state.layout.id
|
||||
|
||||
function offerSvg(noSelfIntersectingLines: boolean): string {
|
||||
let numberOfFeatures = state.featureSummary.totalNumberOfFeatures
|
||||
|
||||
async function getGeojson() {
|
||||
await state.indexedFeatures.downloadAll()
|
||||
return downloadHelper.getCleanGeoJson(metaIsIncluded)
|
||||
}
|
||||
|
||||
async function offerSvg(noSelfIntersectingLines: boolean): Promise<string> {
|
||||
await state.indexedFeatures.downloadAll()
|
||||
const maindiv = document.getElementById("maindiv")
|
||||
const layers = state.layout.layers.filter((l) => l.source !== null)
|
||||
return downloadHelper.asSvg({
|
||||
|
|
@ -34,6 +41,8 @@
|
|||
|
||||
{#if $isLoading}
|
||||
<Loading />
|
||||
{:else if $numberOfFeatures > 100000}
|
||||
<Tr cls="alert" t={Translations.t.general.download.toMuch} />
|
||||
{:else}
|
||||
<div class="flex w-full flex-col" />
|
||||
<h3>
|
||||
|
|
@ -44,20 +53,18 @@
|
|||
{state}
|
||||
extension="geojson"
|
||||
mimetype="application/vnd.geo+json"
|
||||
construct={(geojson) => JSON.stringify(geojson)}
|
||||
construct={async () => JSON.stringify(await getGeojson())}
|
||||
mainText={t.downloadGeojson}
|
||||
helperText={t.downloadGeoJsonHelper}
|
||||
{metaIsIncluded}
|
||||
/>
|
||||
|
||||
<DownloadButton
|
||||
{state}
|
||||
extension="csv"
|
||||
mimetype="text/csv"
|
||||
construct={(geojson) => GeoOperations.toCSV(geojson)}
|
||||
construct={async () => GeoOperations.toCSV(await getGeojson())}
|
||||
mainText={t.downloadCSV}
|
||||
helperText={t.downloadCSVHelper}
|
||||
{metaIsIncluded}
|
||||
/>
|
||||
|
||||
<label class="mb-8 mt-2">
|
||||
|
|
@ -67,7 +74,6 @@
|
|||
|
||||
<DownloadButton
|
||||
{state}
|
||||
{metaIsIncluded}
|
||||
extension="svg"
|
||||
mimetype="image/svg+xml"
|
||||
mainText={t.downloadAsSvg}
|
||||
|
|
@ -77,7 +83,6 @@
|
|||
|
||||
<DownloadButton
|
||||
{state}
|
||||
{metaIsIncluded}
|
||||
extension="svg"
|
||||
mimetype="image/svg+xml"
|
||||
mainText={t.downloadAsSvgLinesOnly}
|
||||
|
|
@ -87,7 +92,6 @@
|
|||
|
||||
<DownloadButton
|
||||
{state}
|
||||
{metaIsIncluded}
|
||||
extension="png"
|
||||
mimetype="image/png"
|
||||
mainText={t.downloadAsPng}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
let t = Translations.t.general.download
|
||||
const downloadHelper = new DownloadHelper(state)
|
||||
|
||||
async function constructPdf(_, title: string, status: UIEventSource<string>) {
|
||||
async function constructPdf(title: string, status: UIEventSource<string>): Promise<Blob> {
|
||||
title =
|
||||
title.substring(0, title.length - 4) + "_" + template.format + "_" + template.orientation
|
||||
const templateUrls = SvgToPdf.templates[templateName].pages
|
||||
|
|
@ -33,11 +33,11 @@
|
|||
console.log("Creating an image for key", key)
|
||||
if (key === "qr") {
|
||||
const toShare = window.location.href.split("#")[0]
|
||||
return new Qr(toShare).toImageElement(parseFloat(width), parseFloat(height))
|
||||
return new Qr(toShare).toImageElement(parseFloat(width))
|
||||
}
|
||||
return downloadHelper.createImage(key, width, height)
|
||||
},
|
||||
textSubstitutions: <Record<string, string>>{
|
||||
textSubstitutions: <Record<string, string | Translation>>{
|
||||
"layout.title": state.layout.title,
|
||||
layoutid: state.layout.id,
|
||||
title: state.layout.title,
|
||||
|
|
@ -61,7 +61,6 @@
|
|||
construct={constructPdf}
|
||||
extension="pdf"
|
||||
helperText={t.downloadAsPdfHelper}
|
||||
metaIsIncluded={false}
|
||||
mainText={t.pdf.current_view_generic.Subs({
|
||||
orientation: template.orientation,
|
||||
paper_size: template.format.toUpperCase(),
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
|
||||
if (!rangeIsShown) {
|
||||
new ShowDataLayer(map, {
|
||||
layer: new LayerConfig(boundsdisplay),
|
||||
layer: new LayerConfig(<any> boundsdisplay),
|
||||
features: new StaticFeatureSource([
|
||||
turf.circle(c, maxDistanceInMeters, {
|
||||
units: "meters",
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import Combine from "../../Base/Combine"
|
|||
import Wikidata from "../../../Logic/Web/Wikidata"
|
||||
import WikidataSearchBox from "../../Wikipedia/WikidataSearchBox"
|
||||
import { Validator } from "../Validator"
|
||||
import { Translation } from "../../i18n/Translation"
|
||||
import Translations from "../../i18n/Translations"
|
||||
|
||||
export default class WikidataValidator extends Validator {
|
||||
constructor() {
|
||||
|
|
@ -12,12 +14,23 @@ export default class WikidataValidator extends Validator {
|
|||
if (str === undefined) {
|
||||
return false
|
||||
}
|
||||
if (str.length <= 2) {
|
||||
if (str.length == 1) {
|
||||
return false
|
||||
}
|
||||
return !str.split(";").some((str) => Wikidata.ExtractKey(str) === undefined)
|
||||
}
|
||||
|
||||
getFeedback(s: string, _?: () => string): Translation | undefined {
|
||||
const t = Translations.t.validation.wikidata
|
||||
if (s === "") {
|
||||
return t.empty
|
||||
}
|
||||
if (!s.match(/(Q[0-9]+)(;Q[0-9]+)*/)) {
|
||||
return t.startsWithQ
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
public reformat(str) {
|
||||
if (str === undefined) {
|
||||
return undefined
|
||||
|
|
|
|||
|
|
@ -92,7 +92,6 @@
|
|||
state.selectedElement.setData(undefined)
|
||||
// When aborted, we force the contributors to place the pin _again_
|
||||
// This is because there might be a nearby object that was disabled; this forces them to re-evaluate the map
|
||||
state.lastClickObject.features.setData([])
|
||||
preciseInputIsTapped = false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ import { OsmTags } from "../Models/OsmFeature"
|
|||
import FavouritesFeatureSource from "../Logic/FeatureSource/Sources/FavouritesFeatureSource"
|
||||
import { ProvidedImage } from "../Logic/ImageProviders/ImageProvider"
|
||||
import GeoLocationHandler from "../Logic/Actors/GeoLocationHandler"
|
||||
import { SummaryTileSourceRewriter } from "../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource"
|
||||
import LayoutSource from "../Logic/FeatureSource/Sources/LayoutSource"
|
||||
|
||||
/**
|
||||
* The state needed to render a special Visualisation.
|
||||
|
|
@ -30,12 +32,13 @@ export interface SpecialVisualizationState {
|
|||
readonly featureSwitches: FeatureSwitchState
|
||||
|
||||
readonly layerState: LayerState
|
||||
readonly featureSummary: SummaryTileSourceRewriter
|
||||
readonly featureProperties: {
|
||||
getStore(id: string): UIEventSource<Record<string, string>>
|
||||
trackFeature?(feature: { properties: OsmTags })
|
||||
}
|
||||
|
||||
readonly indexedFeatures: IndexedFeatureSource
|
||||
readonly indexedFeatures: IndexedFeatureSource & LayoutSource
|
||||
/**
|
||||
* Some features will create a new element that should be displayed.
|
||||
* These can be injected by appending them to this featuresource (and pinging it)
|
||||
|
|
@ -76,7 +79,6 @@ export interface SpecialVisualizationState {
|
|||
readonly preferencesAsTags: UIEventSource<Record<string, string>>
|
||||
readonly language: UIEventSource<string>
|
||||
}
|
||||
readonly lastClickObject: WritableFeatureSource
|
||||
|
||||
readonly availableLayers: Store<RasterLayerPolygon[]>
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import mcChanges from "../../src/assets/generated/themes/mapcomplete-changes.jso
|
|||
import SvelteUIElement from "./Base/SvelteUIElement"
|
||||
import Filterview from "./BigComponents/Filterview.svelte"
|
||||
import FilteredLayer from "../Models/FilteredLayer"
|
||||
import DownloadButton from "./DownloadFlow/DownloadButton.svelte"
|
||||
import { SubtleButton } from "./Base/SubtleButton"
|
||||
import { GeoOperations } from "../Logic/GeoOperations"
|
||||
import { Polygon } from "geojson"
|
||||
|
|
|
|||
|
|
@ -1,39 +1,5 @@
|
|||
<script lang="ts">
|
||||
// Testing grounds
|
||||
|
||||
import { Stores } from "../Logic/UIEventSource"
|
||||
import { Utils } from "../Utils"
|
||||
import jsonld from "jsonld"
|
||||
import SelectedElementView from "./BigComponents/SelectedElementView.svelte"
|
||||
import * as shop from "../assets/generated/layers/shops.json"
|
||||
import LayerConfig from "../Models/ThemeConfig/LayerConfig"
|
||||
import type { OpeningHour } from "./OpeningHours/OpeningHours"
|
||||
import { OH } from "./OpeningHours/OpeningHours"
|
||||
import type { Geometry } from "geojson"
|
||||
|
||||
const shopLayer = new LayerConfig(<any>shop, "shops")
|
||||
|
||||
|
||||
const colruytUrl = "https://www.colruyt.be/nl/winkelzoeker/colruyt-gent"
|
||||
const url = "https://stores.delhaize.be/nl/ad-delhaize-dok-noord"
|
||||
let data = Stores.FromPromise(fetchJsonLd(url)).mapD(properties => ({
|
||||
...properties,
|
||||
id: properties["website"],
|
||||
shop: "supermarket",
|
||||
_country: "be",
|
||||
}))
|
||||
|
||||
let feature = data.mapD(properties => {
|
||||
return <any>{
|
||||
type: "Feature",
|
||||
properties,
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: properties["geo"],
|
||||
},
|
||||
}
|
||||
})
|
||||
</script>
|
||||
{#if $data}
|
||||
<SelectedElementView layer={shopLayer} selectedElement={$feature} state={undefined} tags={data} />
|
||||
{/if}
|
||||
|
||||
No tests
|
||||
|
|
|
|||
|
|
@ -100,14 +100,18 @@
|
|||
})
|
||||
|
||||
let selectedLayer: Store<LayerConfig> = state.selectedElement.mapD((element) => {
|
||||
if (element.properties.id.startsWith("current_view")) {
|
||||
return currentViewLayer
|
||||
}
|
||||
if (element.properties.id === "location_track") {
|
||||
return layout.layers.find((l) => l.id === "gps_track")
|
||||
}
|
||||
return state.layout.getMatchingLayer(element.properties)
|
||||
})
|
||||
if (element.properties.id.startsWith("current_view")) {
|
||||
return currentViewLayer
|
||||
}
|
||||
if(element.properties.id === "new_point_dialog"){
|
||||
return layout.layers.find(l => l.id === "last_click")
|
||||
}
|
||||
if(element.properties.id === "location_track"){
|
||||
return layout.layers.find(l => l.id === "gps_track")
|
||||
}
|
||||
return state.layout.getMatchingLayer(element.properties)
|
||||
},
|
||||
)
|
||||
let currentZoom = state.mapProperties.zoom
|
||||
let showCrosshair = state.userRelatedState.showCrosshair
|
||||
let visualFeedback = state.visualFeedback
|
||||
|
|
@ -266,7 +270,7 @@
|
|||
<div class="flex w-full items-end justify-between px-4">
|
||||
<div class="flex flex-col">
|
||||
<If condition={featureSwitches.featureSwitchEnableLogin}>
|
||||
{#if state.lastClickObject.hasPresets || state.lastClickObject.hasNoteLayer}
|
||||
{#if state.layout.hasPresets() || state.layout.hasNoteLayer()}
|
||||
<button
|
||||
class="pointer-events-auto w-fit"
|
||||
class:disabled={$currentZoom < Constants.minZoomLevelToAddNewPoint}
|
||||
|
|
@ -277,7 +281,7 @@
|
|||
>
|
||||
{#if $currentZoom < Constants.minZoomLevelToAddNewPoint}
|
||||
<Tr t={Translations.t.general.add.zoomInFurther} />
|
||||
{:else if state.lastClickObject.hasPresets}
|
||||
{:else if state.layout.hasPresets()}
|
||||
<Tr t={Translations.t.general.add.title} />
|
||||
{:else}
|
||||
<Tr t={Translations.t.notes.addAComment} />
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue