diff --git a/assets/layers/geocoded_image/geocoded_image.json b/assets/layers/geocoded_image/geocoded_image.json new file mode 100644 index 000000000..c5a9d8b13 --- /dev/null +++ b/assets/layers/geocoded_image/geocoded_image.json @@ -0,0 +1,70 @@ +{ + "id": "geocoded_image", + "source": "special", + "name": null, + "tagRenderings": [], + "pointRendering": [ + { + "location": [ + "point", + "centroid" + ], + "marker": [ + { + "icon": "direction_gradient", + "color": { + "render": "#44cc22", + "mappings": [ + { + "if": "selected=yes", + "then": "#cccc22" + } + ] + } + } + ], + "rotation": "{rotation}deg", + "rotationAlignment": "map", + "pitchAlignment": "map", + "iconSize": "60,60" + }, + { + "location": [ + "point", + "centroid" + ], + "rotationAlignment": "map", + "pitchAlignment": "map", + "marker": [ + { + "icon": "circle", + "color": { + "render": "#44cc22", + "mappings": [ + { + "if": "selected=yes", + "then": "#cccc22" + } + ] + } + } + ], + "iconSize": "14,14" + }, + { + "location": [ + "point", + "centroid" + ], + "rotationAlignment": "map", + "pitchAlignment": "map", + "marker": [ + { + "icon": "ring", + "color": "#000" + } + ], + "iconSize": "14,14" + } + ] +} diff --git a/assets/svg/circle.svg b/assets/svg/circle.svg index 0da26a179..4de11e39b 100644 --- a/assets/svg/circle.svg +++ b/assets/svg/circle.svg @@ -32,7 +32,7 @@ inkscape:window-maximized="1" inkscape:current-layer="svg1" /> diff --git a/assets/svg/direction_gradient.svg b/assets/svg/direction_gradient.svg index 35a51d34a..9d9d2e414 100644 --- a/assets/svg/direction_gradient.svg +++ b/assets/svg/direction_gradient.svg @@ -1,101 +1,55 @@ - - - - - - - - - - - - - + version="1.0" + width="860.50732pt" + height="860.50732pt" + viewBox="0 0 860.50732 860.50732" + preserveAspectRatio="xMidYMid meet" + id="svg14" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns="http://www.w3.org/2000/svg" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:dc="http://purl.org/dc/elements/1.1/"> + + + + + + + + Created by potrace 1.15, written by Peter Selinger 2001-2017 - - image/svg+xml - - - - - - + + image/svg+xml + + + + + diff --git a/assets/themes/velopark/velopark.json b/assets/themes/velopark/velopark.json index d027c7075..47799add7 100644 --- a/assets/themes/velopark/velopark.json +++ b/assets/themes/velopark/velopark.json @@ -234,7 +234,7 @@ { "id": "nearby_images", "render": { - "*": "{nearby_images(open,readonly)}" + "*": "{nearby_images(,readonly)}" } } ], diff --git a/package.json b/package.json index d72175767..33e8e6584 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mapcomplete", - "version": "0.46.4", + "version": "0.46.5", "repository": "https://github.com/pietervdvn/MapComplete", "description": "A small website to edit OSM easily", "bugs": "https://github.com/pietervdvn/MapComplete/issues", @@ -91,7 +91,7 @@ "generate:contributor-list": "vite-node scripts/generateContributors.ts", "generate:service-worker": "tsc src/service-worker.ts --outFile public/service-worker.js && git_hash=$(git rev-parse HEAD) && sed -i.bak \"s/GITHUB-COMMIT/$git_hash/\" public/service-worker.js && rm public/service-worker.js.bak", "reset:layeroverview": "npm run prep:layeroverview && npm run generate:layeroverview && npm run refresh:layeroverview", - "prep:layeroverview": "mkdir -p ./src/assets/generated/layers; echo {\\\"themes\\\":[]} > ./src/assets/generated/known_themes.json && echo {\\\"layers\\\": []} > ./src/assets/generated/known_layers.json && rm -f ./src/assets/generated/layers/*.json && rm -f ./src/assets/generated/themes/*.json && cp ./assets/layers/usersettings/usersettings.json ./src/assets/generated/layers/usersettings.json && echo '{}' > ./src/assets/generated/layers/favourite.json && echo '{}' > ./src/assets/generated/layers/summary.json && echo '{}' > ./src/assets/generated/layers/last_click.json && echo '[]' > ./src/assets/generated/theme_overview.json", + "prep:layeroverview": "mkdir -p ./src/assets/generated/layers; echo {\\\"themes\\\":[]} > ./src/assets/generated/known_themes.json && echo {\\\"layers\\\": []} > ./src/assets/generated/known_layers.json && rm -f ./src/assets/generated/layers/*.json && rm -f ./src/assets/generated/themes/*.json && cp ./assets/layers/usersettings/usersettings.json ./src/assets/generated/layers/usersettings.json && echo '{}' > ./src/assets/generated/layers/favourite.json && echo '{}' > ./src/assets/generated/layers/summary.json && echo '{}' > ./src/assets/generated/layers/last_click.json && echo '[]' > ./src/assets/generated/theme_overview.json echo '{}' > ./src/assets/generated/geocoded_image.json", "generate": "npm run generate:licenses && npm run generate:images && npm run generate:charging-stations && npm run generate:translations && npm run refresh:layeroverview && npm run generate:service-worker", "generate:charging-stations": "cd ./assets/layers/charging_station && vite-node csvToJson.ts && cd -", "clean:tests": "find . -type f -name \"*.doctest.ts\" | xargs -r rm", diff --git a/public/css/index-tailwind-output.css b/public/css/index-tailwind-output.css index bdf385513..515ed0225 100644 --- a/public/css/index-tailwind-output.css +++ b/public/css/index-tailwind-output.css @@ -1761,14 +1761,14 @@ input[type="range"].range-lg::-moz-range-thumb { height: 3.5rem; } -.h-16 { - height: 4rem; -} - .h-48 { height: 12rem; } +.h-16 { + height: 4rem; +} + .h-40 { height: 10rem; } @@ -3251,11 +3251,6 @@ input[type="range"].range-lg::-moz-range-thumb { background-color: rgb(0 0 0 / var(--tw-bg-opacity)); } -.bg-indigo-100 { - --tw-bg-opacity: 1; - background-color: rgb(229 237 255 / var(--tw-bg-opacity)); -} - .bg-gray-100 { --tw-bg-opacity: 1; background-color: rgb(243 244 246 / var(--tw-bg-opacity)); @@ -3286,6 +3281,11 @@ input[type="range"].range-lg::-moz-range-thumb { background-color: rgb(253 246 178 / var(--tw-bg-opacity)); } +.bg-indigo-100 { + --tw-bg-opacity: 1; + background-color: rgb(229 237 255 / var(--tw-bg-opacity)); +} + .bg-purple-100 { --tw-bg-opacity: 1; background-color: rgb(237 235 254 / var(--tw-bg-opacity)); @@ -4032,6 +4032,10 @@ input[type="range"].range-lg::-moz-range-thumb { padding-right: 1rem; } +.pt-2 { + padding-top: 0.5rem; +} + .pb-1\.5 { padding-bottom: 0.375rem; } @@ -5922,11 +5926,6 @@ svg.apply-fill path { border-color: rgb(209 213 219 / var(--tw-border-opacity)); } -.hover\:bg-indigo-200:hover { - --tw-bg-opacity: 1; - background-color: rgb(205 219 254 / var(--tw-bg-opacity)); -} - .hover\:bg-gray-100:hover { --tw-bg-opacity: 1; background-color: rgb(243 244 246 / var(--tw-bg-opacity)); @@ -5962,6 +5961,11 @@ svg.apply-fill path { background-color: rgb(252 233 106 / var(--tw-bg-opacity)); } +.hover\:bg-indigo-200:hover { + --tw-bg-opacity: 1; + background-color: rgb(205 219 254 / var(--tw-bg-opacity)); +} + .hover\:bg-purple-200:hover { --tw-bg-opacity: 1; background-color: rgb(220 215 254 / var(--tw-bg-opacity)); @@ -8110,6 +8114,10 @@ svg.apply-fill path { height: 2.75rem; } + .sm\:h-32 { + height: 8rem; + } + .sm\:h-64 { height: 16rem; } @@ -8299,6 +8307,10 @@ svg.apply-fill path { display: none; } + .md\:h-64 { + height: 16rem; + } + .md\:h-auto { height: auto; } @@ -8482,6 +8494,11 @@ svg.apply-fill path { padding-bottom: 2rem; } + .md\:px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; + } + .md\:pr-2 { padding-right: 0.5rem; } diff --git a/src/Logic/ImageProviders/ImageProvider.ts b/src/Logic/ImageProviders/ImageProvider.ts index c59251de2..a89ee8600 100644 --- a/src/Logic/ImageProviders/ImageProvider.ts +++ b/src/Logic/ImageProviders/ImageProvider.ts @@ -9,7 +9,14 @@ export interface ProvidedImage { key: string provider: ImageProvider id: string - date?: Date + date?: Date, + /** + * Compass angle of the taken image + * 0 = north, 90° = East + */ + rotation?: number + lat?: number, + lon?: number } export default abstract class ImageProvider { diff --git a/src/Logic/ImageProviders/Mapillary.ts b/src/Logic/ImageProviders/Mapillary.ts index 1a5a51715..3ce222714 100644 --- a/src/Logic/ImageProviders/Mapillary.ts +++ b/src/Logic/ImageProviders/Mapillary.ts @@ -162,12 +162,14 @@ export class Mapillary extends ImageProvider { const metadataUrl = "https://graph.mapillary.com/" + mapillaryId + - "?fields=thumb_1024_url,thumb_original_url,captured_at,creator&access_token=" + + "?fields=thumb_1024_url,thumb_original_url,captured_at,compass_angle,geometry,creator&access_token=" + Constants.mapillary_client_token_v4 const response = await Utils.downloadJsonCached(metadataUrl, 60 * 60) const url = response["thumb_1024_url"] const url_hd = response["thumb_original_url"] const date = new Date() + const rotation = (720 - Number(response["compass_angle"])) % 360 + const geometry = response["geometry"] date.setTime(response["captured_at"]) return { id: "" + mapillaryId, @@ -176,6 +178,9 @@ export class Mapillary extends ImageProvider { provider: this, date, key, + rotation, + lat: geometry.coordinates[1], + lon: geometry.coordinates[0] } } } diff --git a/src/Logic/Web/NearbyImagesSearch.ts b/src/Logic/Web/NearbyImagesSearch.ts index 7cd45e089..c4dc40dcd 100644 --- a/src/Logic/Web/NearbyImagesSearch.ts +++ b/src/Logic/Web/NearbyImagesSearch.ts @@ -56,7 +56,7 @@ export interface P4CPicture { author? license? detailsUrl?: string - direction? + direction?: number, osmTags?: object /*To copy straight into OSM!*/ thumbUrl: string details: { diff --git a/src/Models/Constants.ts b/src/Models/Constants.ts index d1a22e5a1..40c070ad5 100644 --- a/src/Models/Constants.ts +++ b/src/Models/Constants.ts @@ -25,6 +25,7 @@ export default class Constants { "last_click", "favourite", "summary", + "geocoded_image" ] as const /** * Special layers which are not included in a theme by default diff --git a/src/Models/ThemeConfig/Conversion/Validation.ts b/src/Models/ThemeConfig/Conversion/Validation.ts index 521547eac..3e8b1fff9 100644 --- a/src/Models/ThemeConfig/Conversion/Validation.ts +++ b/src/Models/ThemeConfig/Conversion/Validation.ts @@ -718,7 +718,7 @@ export class ValidatePointRendering extends DesugaringStep 0)) { context .enter("location") .err( diff --git a/src/Models/ThemeViewState.ts b/src/Models/ThemeViewState.ts index df2624450..7b49572b2 100644 --- a/src/Models/ThemeViewState.ts +++ b/src/Models/ThemeViewState.ts @@ -5,7 +5,7 @@ import { Store, UIEventSource } from "../Logic/UIEventSource" import { FeatureSource, IndexedFeatureSource, - WritableFeatureSource, + WritableFeatureSource } from "../Logic/FeatureSource/FeatureSource" import { OsmConnection } from "../Logic/Osm/OsmConnection" import { ExportableMap, MapProperties } from "./MapProperties" @@ -51,7 +51,7 @@ import SaveFeatureSourceToLocalStorage from "../Logic/FeatureSource/Actors/SaveF import BBoxFeatureSource from "../Logic/FeatureSource/Sources/TouchesBboxFeatureSource" import ThemeViewStateHashActor from "../Logic/Web/ThemeViewStateHashActor" import NoElementsInViewDetector, { - FeatureViewState, + FeatureViewState } from "../Logic/Actors/NoElementsInViewDetector" import FilteredLayer from "./FilteredLayer" import { PreferredRasterLayerSelector } from "../Logic/Actors/PreferredRasterLayerSelector" @@ -64,13 +64,12 @@ import { GeolocationControlState } from "../UI/BigComponents/GeolocationControl" import Zoomcontrol from "../UI/Zoomcontrol" import { SummaryTileSource, - SummaryTileSourceRewriter, + SummaryTileSourceRewriter } from "../Logic/FeatureSource/TiledFeatureSource/SummaryTileSource" import summaryLayer from "../assets/generated/layers/summary.json" import last_click_layerconfig from "../assets/generated/layers/last_click.json" import { LayerConfigJson } from "./ThemeConfig/Json/LayerConfigJson" -import Locale from "../UI/i18n/Locale" import Hash from "../Logic/Web/Hash" import { GeoOperations } from "../Logic/GeoOperations" import { CombinedFetcher } from "../Logic/Web/NearbyImagesSearch" @@ -154,6 +153,10 @@ export default class ThemeViewState implements SpecialVisualizationState { public readonly toCacheSavers: ReadonlyMap public readonly nearbyImageSearcher: CombinedFetcher + /** + * Geocoded images that should be shown on the main map; probably only the currently hovered image + */ + public readonly geocodedImages: UIEventSource = new UIEventSource([ ]) constructor(layout: LayoutConfig, mvtAvailableLayers: Set) { Utils.initDomPurify() @@ -178,7 +181,7 @@ export default class ThemeViewState implements SpecialVisualizationState { "oauth_token", undefined, "Used to complete the login" - ), + ) }) this.userRelatedState = new UserRelatedState( this.osmConnection, @@ -257,8 +260,8 @@ export default class ThemeViewState implements SpecialVisualizationState { bbox.asGeoJson({ zoom: this.mapProperties.zoom.data, ...this.mapProperties.location.data, - id: "current_view_" + currentViewIndex, - }), + id: "current_view_" + currentViewIndex + }) ] }) ) @@ -275,7 +278,7 @@ export default class ThemeViewState implements SpecialVisualizationState { featurePropertiesStore: this.featureProperties, osmConnection: this.osmConnection, historicalUserLocations: this.geolocation.historicalUserLocations, - featureSwitches: this.featureSwitches, + featureSwitches: this.featureSwitches }, layout?.isLeftRightSensitive() ?? false, (e, extraMsg) => this.reportError(e, extraMsg) @@ -303,7 +306,7 @@ export default class ThemeViewState implements SpecialVisualizationState { "leftover features, such as", features[0].properties ) - }, + } } ) this.perLayer = perLayer.perLayer @@ -359,7 +362,7 @@ export default class ThemeViewState implements SpecialVisualizationState { { currentZoom: this.mapProperties.zoom, layerState: this.layerState, - bounds: this.visualFeedbackViewportBounds, + bounds: this.visualFeedbackViewportBounds } ) this.hasDataInView = new NoElementsInViewDetector(this).hasFeatureInView @@ -453,7 +456,7 @@ export default class ThemeViewState implements SpecialVisualizationState { doShowLayer, metaTags: this.userRelatedState.preferencesAsTags, selectedElement: this.selectedElement, - fetchStore: (id) => this.featureProperties.getStore(id), + fetchStore: (id) => this.featureProperties.getStore(id) }) }) return filteringFeatureSource @@ -480,7 +483,7 @@ export default class ThemeViewState implements SpecialVisualizationState { doShowLayer: flayerGps.isDisplayed, layer: flayerGps.layerDef, metaTags: this.userRelatedState.preferencesAsTags, - selectedElement: this.selectedElement, + selectedElement: this.selectedElement }) } @@ -569,7 +572,7 @@ export default class ThemeViewState implements SpecialVisualizationState { Hotkeys.RegisterHotkey( { nomod: " ", - onUp: true, + onUp: true }, docs.selectItem, () => { @@ -595,7 +598,7 @@ export default class ThemeViewState implements SpecialVisualizationState { Hotkeys.RegisterHotkey( { nomod: "" + i, - onUp: true, + onUp: true }, doc, () => this.selectClosestAtCenter(i - 1) @@ -608,7 +611,7 @@ export default class ThemeViewState implements SpecialVisualizationState { } Hotkeys.RegisterHotkey( { - nomod: "b", + nomod: "b" }, docs.openLayersPanel, () => { @@ -619,7 +622,7 @@ export default class ThemeViewState implements SpecialVisualizationState { ) Hotkeys.RegisterHotkey( { - nomod: "s", + nomod: "s" }, Translations.t.hotkeyDocumentation.openFilterPanel, () => { @@ -697,7 +700,7 @@ export default class ThemeViewState implements SpecialVisualizationState { Hotkeys.RegisterHotkey( { - shift: "T", + shift: "T" }, Translations.t.hotkeyDocumentation.translationMode, () => { @@ -734,7 +737,7 @@ export default class ThemeViewState implements SpecialVisualizationState { this.mapProperties.zoom.map((z) => Math.max(Math.floor(z), 0)), this.mapProperties, { - isActive: this.mapProperties.zoom.map((z) => z < maxzoom), + isActive: this.mapProperties.zoom.map((z) => z < maxzoom) } ) @@ -755,6 +758,7 @@ export default class ThemeViewState implements SpecialVisualizationState { gps_location: this.geolocation.currentUserLocation, gps_location_history: this.geolocation.historicalUserLocations, gps_track: this.geolocation.historicalUserLocationsTrack, + geocoded_image: new StaticFeatureSource(this.geocodedImages), selected_element: new StaticFeatureSource( this.selectedElement.map((f) => (f === undefined ? empty : [f])) ), @@ -766,7 +770,7 @@ export default class ThemeViewState implements SpecialVisualizationState { current_view: this.currentView, favourite: this.favourites, summary: this.featureSummary, - last_click: this.lastClickObject, + last_click: this.lastClickObject } this.closestFeatures.registerSource(specialLayers.favourite, "favourite") @@ -821,7 +825,7 @@ export default class ThemeViewState implements SpecialVisualizationState { doShowLayer: flayer.isDisplayed, layer: flayer.layerDef, metaTags: this.userRelatedState.preferencesAsTags, - selectedElement: this.selectedElement, + selectedElement: this.selectedElement }) }) const summaryLayerConfig = new LayerConfig(summaryLayer, "summaryLayer") @@ -829,7 +833,7 @@ export default class ThemeViewState implements SpecialVisualizationState { features: specialLayers.summary, layer: summaryLayerConfig, // doShowLayer: this.mapProperties.zoom.map((z) => z < maxzoom), - selectedElement: this.selectedElement, + selectedElement: this.selectedElement }) const lastClickLayerConfig = new LayerConfig( @@ -840,14 +844,14 @@ export default class ThemeViewState implements SpecialVisualizationState { lastClickLayerConfig.isShown === undefined ? specialLayers.last_click : specialLayers.last_click.features.mapD((fs) => - fs.filter((f) => { - const matches = lastClickLayerConfig.isShown.matchesProperties( - f.properties - ) - console.debug("LastClick ", f, "matches", matches) - return matches - }) - ) + fs.filter((f) => { + const matches = lastClickLayerConfig.isShown.matchesProperties( + f.properties + ) + console.debug("LastClick ", f, "matches", matches) + return matches + }) + ) new ShowDataLayer(this.map, { features: new StaticFeatureSource(lastClickFiltered), layer: lastClickLayerConfig, @@ -858,9 +862,9 @@ export default class ThemeViewState implements SpecialVisualizationState { } this.map.data.flyTo({ zoom: Constants.minZoomLevelToAddNewPoint, - center: GeoOperations.centerpointCoordinates(feature), + center: GeoOperations.centerpointCoordinates(feature) }) - }, + } }) } @@ -871,6 +875,7 @@ export default class ThemeViewState implements SpecialVisualizationState { this.selectedElement.addCallback((selected) => { if (selected === undefined) { this.focusOnMap() + this.geocodedImages.set([]) } else { this.lastClickObject.clear() } @@ -953,8 +958,8 @@ export default class ThemeViewState implements SpecialVisualizationState { userid: this.osmConnection.userDetails.data?.uid, pendingChanges: this.changes.pendingChanges.data, previousChanges: this.changes.allChanges.data, - changeRewrites: Utils.MapToObj(this.changes._changesetHandler._remappings), - }), + changeRewrites: Utils.MapToObj(this.changes._changesetHandler._remappings) + }) }) } catch (e) { console.error("Could not upload an error report") diff --git a/src/UI/Comparison/ComparisonTable.svelte b/src/UI/Comparison/ComparisonTable.svelte index 9c20fb2bf..0a8bc1626 100644 --- a/src/UI/Comparison/ComparisonTable.svelte +++ b/src/UI/Comparison/ComparisonTable.svelte @@ -132,6 +132,7 @@
{#each $unknownImages as image (image)} let fallbackImage: string = undefined @@ -20,19 +22,43 @@ let imgEl: HTMLImageElement export let imgClass: string = undefined + export let state: SpecialVisualizationState = undefined export let attributionFormat: "minimal" | "medium" | "large" = "medium" export let previewedImage: UIEventSource export let canZoom = previewedImage !== undefined let loaded = false - let showBigPreview = new UIEventSource(false) - onDestroy(showBigPreview.addCallbackAndRun(shown=>{ - if(!shown){ + let showBigPreview = new UIEventSource(false) + onDestroy(showBigPreview.addCallbackAndRun(shown => { + if (!shown) { previewedImage.set(false) } })) onDestroy(previewedImage.addCallbackAndRun(previewedImage => { showBigPreview.set(previewedImage?.id === image.id) })) + + function highlight(entered: boolean = true) { + if (!entered) { + state?.geocodedImages.set([]) + return + } + if (isNaN(image.lon) || isNaN(image.lat)) { + return + } + const f: Feature = { + type: "Feature", + properties: { + id: image.id, + rotation: image.rotation + }, + geometry: { + type: "Point", + coordinates: [image.lon, image.lat] + } + } + console.log(f) + state?.geocodedImages.set([f]) + } @@ -48,7 +74,10 @@
-
+
highlight()} + on:mouseleave={() => highlight(false)} + > (loaded = true)} @@ -68,7 +97,7 @@ {#if canZoom && loaded}
previewedImage.set(image)}> + on:click={() => previewedImage.set(image)}>
{/if} diff --git a/src/UI/Image/ImageCarousel.ts b/src/UI/Image/ImageCarousel.ts index 9b0ee5d21..8c6d1e5b9 100644 --- a/src/UI/Image/ImageCarousel.ts +++ b/src/UI/Image/ImageCarousel.ts @@ -8,7 +8,6 @@ import ImageProvider, { ProvidedImage } from "../../Logic/ImageProviders/ImagePr import { OsmConnection } from "../../Logic/Osm/OsmConnection" import { Changes } from "../../Logic/Osm/Changes" import LayoutConfig from "../../Models/ThemeConfig/LayoutConfig" -import { Feature } from "geojson" import SvelteUIElement from "../Base/SvelteUIElement" import AttributedImage from "./AttributedImage.svelte" @@ -30,6 +29,7 @@ export class ImageCarousel extends Toggle { try { let image: BaseUIElement = new SvelteUIElement(AttributedImage, { image: url, + state, previewedImage: state?.previewedImage, }) diff --git a/src/UI/Image/LinkableImage.svelte b/src/UI/Image/LinkableImage.svelte index b290ecf95..8112abfb7 100644 --- a/src/UI/Image/LinkableImage.svelte +++ b/src/UI/Image/LinkableImage.svelte @@ -14,8 +14,8 @@ import AttributedImage from "./AttributedImage.svelte" import SpecialTranslation from "../Popup/TagRendering/SpecialTranslation.svelte" import LoginToggle from "../Base/LoginToggle.svelte" - import ImagePreview from "./ImagePreview.svelte" - import FloatOver from "../Base/FloatOver.svelte" + import { onDestroy } from "svelte" + import { Utils } from "../../Utils" export let tags: UIEventSource export let state: SpecialVisualizationState @@ -23,6 +23,8 @@ export let feature: Feature export let layer: LayerConfig + export let highlighted: UIEventSource = undefined + export let linkable = true let targetValue = Object.values(image.osmTags)[0] let isLinked = new UIEventSource(Object.values(tags.data).some((v) => targetValue === v)) @@ -33,7 +35,7 @@ key: undefined, provider: AllImageProviders.byName(image.provider), date: new Date(image.date), - id: Object.values(image.osmTags)[0], + id: Object.values(image.osmTags)[0] } async function applyLink(isLinked: boolean) { @@ -44,7 +46,7 @@ if (isLinked) { const action = new LinkImageAction(currentTags.id, key, url, tags, { theme: tags.data._orig_theme ?? state.layout.id, - changeType: "link-image", + changeType: "link-image" }) await state.changes.applyAction(action) } else { @@ -53,7 +55,7 @@ if (v === url) { const action = new ChangeTagAction(currentTags.id, new Tag(k, ""), currentTags, { theme: tags.data._orig_theme ?? state.layout.id, - changeType: "remove-image", + changeType: "remove-image" }) state.changes.applyAction(action) } @@ -62,16 +64,30 @@ } isLinked.addCallback((isLinked) => applyLink(isLinked)) + + let element: HTMLDivElement + if (highlighted) { + + onDestroy( + highlighted.addCallbackD(highlightedUrl => { + if (highlightedUrl === image.pictureUrl) { + Utils.scrollIntoView(element) + } + }) + ) + }
diff --git a/src/UI/Image/NearbyImages.svelte b/src/UI/Image/NearbyImages.svelte index 665490c2b..d70dc2ba5 100644 --- a/src/UI/Image/NearbyImages.svelte +++ b/src/UI/Image/NearbyImages.svelte @@ -7,13 +7,23 @@ import type { SpecialVisualizationState } from "../SpecialVisualization" import type { P4CPicture } from "../../Logic/Web/NearbyImagesSearch" import LinkableImage from "./LinkableImage.svelte" - import type { Feature } from "geojson" + import type { Feature, Point } from "geojson" import LayerConfig from "../../Models/ThemeConfig/LayerConfig" import Loading from "../Base/Loading.svelte" import AllImageProviders from "../../Logic/ImageProviders/AllImageProviders" import Tr from "../Base/Tr.svelte" import Translations from "../i18n/Translations" import MapillaryLink from "../BigComponents/MapillaryLink.svelte" + import MaplibreMap from "../Map/MaplibreMap.svelte" + import { Map as MlMap } from "maplibre-gl" + import { MapLibreAdaptor } from "../Map/MapLibreAdaptor" + import ShowDataLayer from "../Map/ShowDataLayer" + import StaticFeatureSource from "../../Logic/FeatureSource/Sources/StaticFeatureSource" + import * as geocoded_image from "../../assets/generated/layers/geocoded_image.json" + import type { LayerConfigJson } from "../../Models/ThemeConfig/Json/LayerConfigJson" + import { onDestroy } from "svelte" + import { BBox } from "../../Logic/BBox" + export let tags: UIEventSource export let state: SpecialVisualizationState @@ -42,12 +52,100 @@ [loadedImages] ) + let asFeatures = result.map(p4cs => p4cs.map(p4c => (>{ + type: "Feature", + geometry: { + type: "Point", + coordinates: [p4c.coordinates.lng, p4c.coordinates.lat] + }, + properties: { + id: p4c.pictureUrl, + rotation: p4c.direction + } + }))) + + let selected = new UIEventSource(undefined) + let selectedAsFeature = selected.mapD(s => { + return [>{ + type: "Feature", + geometry: { + type: "Point", + coordinates: [s.coordinates.lng, s.coordinates.lat] + }, + properties: { + id: s.pictureUrl, + selected: "yes", + rotation: s.direction + } + }] + }) + let someLoading = imageState.state.mapD((stateRecord) => Object.values(stateRecord).some((v) => v === "loading") ) let errors = imageState.state.mapD((stateRecord) => Object.keys(stateRecord).filter((k) => stateRecord[k] === "error") ) + let highlighted = new UIEventSource(undefined) + + onDestroy(highlighted.addCallbackD(hl => { + const p4c = result.data?.find(i => i.pictureUrl === hl) + selected.set(p4c) + } + )) + + let map: UIEventSource = new UIEventSource(undefined) + let mapProperties = new MapLibreAdaptor(map, { + rasterLayer: state.mapProperties.rasterLayer, + rotation: state.mapProperties.rotation, + pitch: state.mapProperties.pitch, + zoom: new UIEventSource(16), + location: new UIEventSource({ lon, lat }), + }) + + + const geocodedImageLayer = new LayerConfig(geocoded_image) + new ShowDataLayer(map, { + features: new StaticFeatureSource(asFeatures), + layer: geocodedImageLayer, + zoomToFeatures: true, + onClick: (feature) => { + highlighted.set(feature.properties.id) + } + }) + + + ShowDataLayer.showMultipleLayers( + map, + new StaticFeatureSource([feature]), + state.layout.layers + ) + + onDestroy( + asFeatures.addCallbackAndRunD(features => { + if(features.length == 0){ + return + } + let bbox = BBox.get(features[0]) + for (const f of features) { + bbox = bbox.unionWith(BBox.get(f)) + } + mapProperties.maxbounds.set(bbox.pad(1.1)) + }) + + ) + + new ShowDataLayer(map, { + features: new StaticFeatureSource(selectedAsFeature), + layer: geocodedImageLayer, + onClick: (feature) => { + highlighted.set(feature.properties.id) + } + }) + + + +
@@ -62,12 +160,24 @@ {:else}
{#each $result as image (image.pictureUrl)} - - + {highlighted.set(image.pictureUrl)}} + on:mouseleave={() =>{ highlighted.set(undefined); selected.set(undefined)}} + > + {/each}
{/if} + + + + + +
{#if $someLoading && $result.length > 0} @@ -80,9 +190,10 @@ /> {/if}
- +
+ + +
+
diff --git a/src/UI/Image/NearbyImagesCollapsed.svelte b/src/UI/Image/NearbyImagesCollapsed.svelte index 5e647655c..10a7c1c1b 100644 --- a/src/UI/Image/NearbyImagesCollapsed.svelte +++ b/src/UI/Image/NearbyImagesCollapsed.svelte @@ -11,8 +11,9 @@ import Camera_plus from "../../assets/svg/Camera_plus.svelte" import LoginToggle from "../Base/LoginToggle.svelte" import { ariaLabel } from "../../Utils/ariaLabel" - import { Accordion, AccordionItem } from "flowbite-svelte" + import { Accordion, AccordionItem, Modal } from "flowbite-svelte" import AccordionSingle from "../Flowbite/AccordionSingle.svelte" + import Popup from "../Base/Popup.svelte" export let tags: UIEventSource export let state: SpecialVisualizationState @@ -24,15 +25,16 @@ export let layer: LayerConfig const t = Translations.t.image.nearby - let expanded = false let enableLogin = state.featureSwitches.featureSwitchEnableLogin + export let shown = new UIEventSource(false) {#if enableLogin.data} - - + + + - + {/if} diff --git a/src/UI/Map/Icon.svelte b/src/UI/Map/Icon.svelte index a6bef31f6..fa78d5b09 100644 --- a/src/UI/Map/Icon.svelte +++ b/src/UI/Map/Icon.svelte @@ -121,7 +121,7 @@ {:else if icon === "confirm"} - {:else if icon === "direction"} + {:else if icon === "direction" || icon === "direction_gradient"} {:else if icon === "not_found"} diff --git a/src/UI/Map/ShowDataLayer.ts b/src/UI/Map/ShowDataLayer.ts index 063fb6ba1..e5318829b 100644 --- a/src/UI/Map/ShowDataLayer.ts +++ b/src/UI/Map/ShowDataLayer.ts @@ -159,10 +159,9 @@ class PointRenderingLayer { }) if (this._onClick) { - const self = this - el.addEventListener("click", function (ev) { + el.addEventListener("click", (ev)=> { ev.preventDefault() - self._onClick(feature) + this._onClick(feature) // Workaround to signal the MapLibreAdaptor to ignore this click ev["consumed"] = true }) diff --git a/src/UI/SpecialVisualization.ts b/src/UI/SpecialVisualization.ts index ae1512114..8d58c14fd 100644 --- a/src/UI/SpecialVisualization.ts +++ b/src/UI/SpecialVisualization.ts @@ -93,6 +93,7 @@ export interface SpecialVisualizationState { readonly previewedImage: UIEventSource readonly nearbyImageSearcher: CombinedFetcher readonly geolocation: GeoLocationHandler + readonly geocodedImages : UIEventSource showCurrentLocationOn(map: Store): ShowDataLayer reportError(message: string): Promise diff --git a/src/assets/svg/Direction_gradient.svelte b/src/assets/svg/Direction_gradient.svelte index e9d7b36f1..804d626a3 100644 --- a/src/assets/svg/Direction_gradient.svelte +++ b/src/assets/svg/Direction_gradient.svelte @@ -1,4 +1,4 @@ - Created by potrace 1.15, written by Peter Selinger 2001-2017 image/svg+xml \ No newline at end of file + Created by potrace 1.15, written by Peter Selinger 2001-2017 image/svg+xml \ No newline at end of file diff --git a/src/assets/svg/Unsnap.svelte b/src/assets/svg/Unsnap.svelte new file mode 100644 index 000000000..392897c01 --- /dev/null +++ b/src/assets/svg/Unsnap.svelte @@ -0,0 +1,4 @@ + + \ No newline at end of file