Feature: show geocoded images on the map when hovered, show interactive minimap on nearbyImages element

This commit is contained in:
Pieter Vander Vennet 2024-09-12 01:31:00 +02:00
parent d079ba91aa
commit f3fdc95bd0
23 changed files with 404 additions and 182 deletions

View file

@ -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"
}
]
}

View file

@ -32,7 +32,7 @@
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
style="fill:#000000"
style="fill:#000000;"
class="selectable"
d="M 375,187.5 C 375,291.05469 291.05469,375 187.5,375 83.945312,375 0,291.05469 0,187.5 0,83.945312 83.945312,0 187.5,0 291.05469,0 375,83.945312 375,187.5 Z m 0,0"
id="path1" />

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -1,101 +1,55 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns="http://www.w3.org/2000/svg"
version="1.0"
width="860.50732pt"
height="860.50732pt"
viewBox="0 0 860.50732 860.50732"
preserveAspectRatio="xMidYMid meet"
id="svg14"
sodipodi:docname="direction_gradient.svg"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
<defs
id="defs18">
<linearGradient
inkscape:collect="always"
id="linearGradient832">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop828"/>
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop830"/>
</linearGradient>
<radialGradient
inkscape:collect="always"
xlink:href="#linearGradient832"
id="radialGradient838"
cx="430.25363"
cy="519.61188"
fx="430.25363"
fy="519.61188"
r="305.54589"
gradientTransform="matrix(0.95288409,-0.94890664,0.94542304,0.94938587,-470.98122,345.21193)"
gradientUnits="userSpaceOnUse"/>
</defs>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="999"
id="namedview16"
showgrid="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:zoom="0.70710678"
inkscape:cx="279.00239"
inkscape:cy="856.75313"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg14">
<sodipodi:guide
position="430.25363,862.49682"
orientation="1,0"
id="guide832"
inkscape:locked="false"/>
<sodipodi:guide
position="-42.427977,430.25368"
orientation="0,1"
id="guide834"
inkscape:locked="false"/>
<sodipodi:guide
position="398.27788,720.18823"
orientation="0,1"
id="guide840"
inkscape:locked="false"/>
</sodipodi:namedview>
<metadata
id="metadata2">
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/">
<defs
id="defs18">
<linearGradient
id="linearGradient832">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop828" />
<stop
style="stop-color:#000000;stop-opacity:0;"
offset="1"
id="stop830" />
</linearGradient>
<radialGradient
xlink:href="#linearGradient832"
id="radialGradient838"
cx="430.25363"
cy="519.61188"
fx="430.25363"
fy="519.61188"
r="305.54589"
gradientTransform="matrix(0.95288409,-0.94890664,0.94542304,0.94938587,-470.98122,345.21193)"
gradientUnits="userSpaceOnUse" />
</defs>
<metadata
id="metadata2">
Created by potrace 1.15, written by Peter Selinger 2001-2017
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
<dc:title/>
</cc:Work>
</rdf:RDF>
</metadata>
<path
style="fill:url(#radialGradient838);fill-opacity:1;stroke:none;stroke-width:2.83464575;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 735.79979,124.70799 C 654.79116,43.598883 544.88842,-2.0206645 430.25389,-2.121103 315.61937,-2.0206592 205.71663,43.598888 124.70801,124.70799 l 305.54588,305.54589 z"
id="path836"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc"/>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<path
style="fill:url(#radialGradient838);fill-opacity:1;stroke:none;stroke-width:2.83464575;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 735.79979,124.70799 C 654.79116,43.598883 544.88842,-2.0206645 430.25389,-2.121103 315.61937,-2.0206592 205.71663,43.598888 124.70801,124.70799 l 305.54588,305.54589 z"
id="path836" />
</svg>

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -234,7 +234,7 @@
{
"id": "nearby_images",
"render": {
"*": "{nearby_images(open,readonly)}"
"*": "{nearby_images(,readonly)}"
}
}
],

View file

@ -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",

View file

@ -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;
}

View file

@ -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 {

View file

@ -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 = <string>response["thumb_1024_url"]
const url_hd = <string>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 <ProvidedImage>{
id: "" + mapillaryId,
@ -176,6 +178,9 @@ export class Mapillary extends ImageProvider {
provider: this,
date,
key,
rotation,
lat: geometry.coordinates[1],
lon: geometry.coordinates[0]
}
}
}

View file

@ -56,7 +56,7 @@ export interface P4CPicture {
author?
license?
detailsUrl?: string
direction?
direction?: number,
osmTags?: object /*To copy straight into OSM!*/
thumbUrl: string
details: {

View file

@ -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

View file

@ -718,7 +718,7 @@ export class ValidatePointRendering extends DesugaringStep<PointRenderingConfigJ
if (json.marker && !Array.isArray(json.marker)) {
context.enter("marker").err("The marker in a pointRendering should be an array")
}
if (json.location.length == 0) {
if (!(json.location?.length > 0)) {
context
.enter("location")
.err(

View file

@ -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<string, SaveFeatureSourceToLocalStorage>
public readonly nearbyImageSearcher: CombinedFetcher
/**
* Geocoded images that should be shown on the main map; probably only the currently hovered image
*/
public readonly geocodedImages: UIEventSource<Feature[]> = new UIEventSource<Feature[]>([ ])
constructor(layout: LayoutConfig, mvtAvailableLayers: Set<string>) {
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(<LayerConfigJson>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")

View file

@ -132,6 +132,7 @@
<div class="flex h-32 w-max gap-x-2">
{#each $unknownImages as image (image)}
<AttributedImage
{state}
imgClass="h-32 w-max shrink-0"
image={{ url: image }}
previewedImage={state.previewedImage}

View file

@ -7,10 +7,12 @@
import { Mapillary } from "../../Logic/ImageProviders/Mapillary"
import { UIEventSource } from "../../Logic/UIEventSource"
import { MagnifyingGlassPlusIcon } from "@babeard/svelte-heroicons/outline"
import { CloseButton, Modal } from "flowbite-svelte"
import { CloseButton } from "flowbite-svelte"
import ImageOperations from "./ImageOperations.svelte"
import Popup from "../Base/Popup.svelte"
import { onDestroy } from "svelte"
import type { SpecialVisualizationState } from "../SpecialVisualization"
import type { Feature, Point } from "geojson"
export let image: Partial<ProvidedImage>
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<ProvidedImage>
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<Point> = {
type: "Feature",
properties: {
id: image.id,
rotation: image.rotation
},
geometry: {
type: "Point",
coordinates: [image.lon, image.lat]
}
}
console.log(f)
state?.geocodedImages.set([f])
}
</script>
<Popup shown={showBigPreview} bodyPadding="p-0" dismissable={true}>
@ -48,7 +74,10 @@
</div>
</Popup>
<div class="relative shrink-0">
<div class="relative w-fit">
<div class="relative w-fit"
on:mouseenter={() => highlight()}
on:mouseleave={() => highlight(false)}
>
<img
bind:this={imgEl}
on:load={() => (loaded = true)}
@ -68,7 +97,7 @@
{#if canZoom && loaded}
<div
class="bg-black-transparent absolute right-0 top-0 rounded-bl-full"
on:click={() => previewedImage.set(image)}>
on:click={() => previewedImage.set(image)}>
<MagnifyingGlassPlusIcon class="h-8 w-8 cursor-zoom-in pl-3 pb-3" color="white" />
</div>
{/if}

View file

@ -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,
})

View file

@ -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<OsmTags>
export let state: SpecialVisualizationState
@ -23,6 +23,8 @@
export let feature: Feature
export let layer: LayerConfig
export let highlighted: UIEventSource<string> = 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)
}
})
)
}
</script>
<div
class="flex w-fit shrink-0 flex-col overflow-hidden rounded-lg"
class:border-interactive={$isLinked}
class:border-interactive={$isLinked || $highlighted === image.pictureUrl}
style="border-width: 2px"
bind:this={element}
>
<AttributedImage
{state}
image={providedImage}
imgClass="max-h-64 w-auto"
imgClass="max-h-64 w-auto sm:h-32 md:h-64"
previewedImage={state.previewedImage}
attributionFormat="minimal"
>

View file

@ -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<OsmTags>
export let state: SpecialVisualizationState
@ -42,12 +52,100 @@
[loadedImages]
)
let asFeatures = result.map(p4cs => p4cs.map(p4c => (<Feature<Point>>{
type: "Feature",
geometry: {
type: "Point",
coordinates: [p4c.coordinates.lng, p4c.coordinates.lat]
},
properties: {
id: p4c.pictureUrl,
rotation: p4c.direction
}
})))
let selected = new UIEventSource<P4CPicture>(undefined)
let selectedAsFeature = selected.mapD(s => {
return [<Feature<Point>>{
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<string>(undefined)
onDestroy(highlighted.addCallbackD(hl => {
const p4c = result.data?.find(i => i.pictureUrl === hl)
selected.set(p4c)
}
))
let map: UIEventSource<MlMap> = new UIEventSource<MlMap>(undefined)
let mapProperties = new MapLibreAdaptor(map, {
rasterLayer: state.mapProperties.rasterLayer,
rotation: state.mapProperties.rotation,
pitch: state.mapProperties.pitch,
zoom: new UIEventSource<number>(16),
location: new UIEventSource({ lon, lat }),
})
const geocodedImageLayer = new LayerConfig(<LayerConfigJson>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)
}
})
</script>
<div class="flex flex-col">
@ -62,12 +160,24 @@
{:else}
<div class="flex w-full space-x-4 overflow-x-auto" style="scroll-snap-type: x proximity">
{#each $result as image (image.pictureUrl)}
<span class="w-fit shrink-0" style="scroll-snap-align: start">
<LinkableImage {tags} {image} {state} {feature} {layer} {linkable} />
<span class="w-fit shrink-0" style="scroll-snap-align: start"
on:mouseenter={() => {highlighted.set(image.pictureUrl)}}
on:mouseleave={() =>{ highlighted.set(undefined); selected.set(undefined)}}
>
<LinkableImage {tags} {image} {state} {feature} {layer} {linkable} {highlighted} />
</span>
{/each}
</div>
{/if}
<span class="self-end pt-2">
<MapillaryLink
large={false}
mapProperties={{ zoom: new ImmutableStore(16), location: new ImmutableStore({ lon, lat }) }}
/>
</span>
<div class="my-2 flex justify-between">
<div>
{#if $someLoading && $result.length > 0}
@ -80,9 +190,10 @@
/>
{/if}
</div>
<MapillaryLink
large={false}
mapProperties={{ zoom: new ImmutableStore(16), location: new ImmutableStore({ lon, lat }) }}
/>
</div>
<div class="h-48">
<MaplibreMap interactive={false} {map} {mapProperties} />
</div>
</div>

View file

@ -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<OsmTags>
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)
</script>
{#if enableLogin.data}
<AccordionSingle>
<span slot="header" class="p-2 text-base">
<button on:click={() => {shown.set(!shown.data)}}><Tr t={t.seeNearby}/> </button>
<Popup {shown} bodyPadding="p-4">
<span slot="header">
<Tr t={t.seeNearby} />
</span>
<NearbyImages {tags} {state} {lon} {lat} {feature} {linkable} {layer} />
</AccordionSingle>
</Popup>
{/if}

View file

@ -121,7 +121,7 @@
<HeartOutlineIcon style="--svg-color: {color}" class={twMerge(clss, "apply-fill")} />
{:else if icon === "confirm"}
<Confirm class={clss} {color} />
{:else if icon === "direction"}
{:else if icon === "direction" || icon === "direction_gradient"}
<Direction_gradient class={clss} {color} />
{:else if icon === "not_found"}
<Not_found class={twMerge(clss, "no-image-background")} {color} />

View file

@ -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
})

View file

@ -93,6 +93,7 @@ export interface SpecialVisualizationState {
readonly previewedImage: UIEventSource<ProvidedImage>
readonly nearbyImageSearcher: CombinedFetcher
readonly geolocation: GeoLocationHandler
readonly geocodedImages : UIEventSource<Feature[]>
showCurrentLocationOn(map: Store<MlMap>): ShowDataLayer
reportError(message: string): Promise<void>

View file

@ -1,4 +1,4 @@
<script>
export let color = "#000000"
</script>
<svg {...$$restProps} on:click on:mouseover on:mouseenter on:mouseleave on:keydown on:focus xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns="http://www.w3.org/2000/svg" version="1.0" width="860.50732pt" height="860.50732pt" viewBox="0 0 860.50732 860.50732" preserveAspectRatio="xMidYMid meet" id="svg14" sodipodi:docname="direction_gradient.svg" inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"> <defs id="defs18"> <linearGradient inkscape:collect="always" id="linearGradient832"> <stop style="stop-color:{color};stop-opacity:1;" offset="0" id="stop828"/> <stop style="stop-color:{color};stop-opacity:0;" offset="1" id="stop830"/> </linearGradient> <radialGradient inkscape:collect="always" xlink:href="#linearGradient832" id="radialGradient838" cx="430.25363" cy="519.61188" fx="430.25363" fy="519.61188" r="305.54589" gradientTransform="matrix(0.95288409,-0.94890664,0.94542304,0.94938587,-470.98122,345.21193)" gradientUnits="userSpaceOnUse"/> </defs> <sodipodi:namedview pagecolor="#ffffff" bordercolor="#666666" borderopacity="1" objecttolerance="10" gridtolerance="10" guidetolerance="10" inkscape:pageopacity="0" inkscape:pageshadow="2" inkscape:window-width="1920" inkscape:window-height="999" id="namedview16" showgrid="false" showguides="true" inkscape:guide-bbox="true" inkscape:zoom="0.70710678" inkscape:cx="279.00239" inkscape:cy="856.75313" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="svg14"> <sodipodi:guide position="430.25363,862.49682" orientation="1,0" id="guide832" inkscape:locked="false"/> <sodipodi:guide position="-42.427977,430.25368" orientation="0,1" id="guide834" inkscape:locked="false"/> <sodipodi:guide position="398.27788,720.18823" orientation="0,1" id="guide840" inkscape:locked="false"/> </sodipodi:namedview> <metadata id="metadata2"> Created by potrace 1.15, written by Peter Selinger 2001-2017 <rdf:RDF> <cc:Work rdf:about=""> <dc:format>image/svg+xml</dc:format> <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/> <dc:title/> </cc:Work> </rdf:RDF> </metadata> <path style="fill:url(#radialGradient838);fill-opacity:1;stroke:none;stroke-width:2.83464575;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M 735.79979,124.70799 C 654.79116,43.598883 544.88842,-2.0206645 430.25389,-2.121103 315.61937,-2.0206592 205.71663,43.598888 124.70801,124.70799 l 305.54588,305.54589 z" id="path836" inkscape:connector-curvature="0" sodipodi:nodetypes="ccccc"/> </svg>
<svg {...$$restProps} on:click on:mouseover on:mouseenter on:mouseleave on:keydown on:focus 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/"> <defs id="defs18"> <linearGradient id="linearGradient832"> <stop style="stop-color:{color};stop-opacity:1;" offset="0" id="stop828" /> <stop style="stop-color:{color};stop-opacity:0;" offset="1" id="stop830" /> </linearGradient> <radialGradient xlink:href="#linearGradient832" id="radialGradient838" cx="430.25363" cy="519.61188" fx="430.25363" fy="519.61188" r="305.54589" gradientTransform="matrix(0.95288409,-0.94890664,0.94542304,0.94938587,-470.98122,345.21193)" gradientUnits="userSpaceOnUse" /> </defs> <metadata id="metadata2"> Created by potrace 1.15, written by Peter Selinger 2001-2017 <rdf:RDF> <cc:Work rdf:about=""> <dc:format>image/svg+xml</dc:format> <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> </cc:Work> </rdf:RDF> </metadata> <path style="fill:url(#radialGradient838);fill-opacity:1;stroke:none;stroke-width:2.83464575;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" d="M 735.79979,124.70799 C 654.79116,43.598883 544.88842,-2.0206645 430.25389,-2.121103 315.61937,-2.0206592 205.71663,43.598888 124.70801,124.70799 l 305.54588,305.54589 z" id="path836" /> </svg>

View file

@ -0,0 +1,4 @@
<script>
export let color = "#000000"
</script>
<!-- Created with Inkscape (http://www.inkscape.org/) --> <svg {...$$restProps} on:click on:mouseover on:mouseenter on:mouseleave on:keydown on:focus width="120" height="120" viewBox="0 0 120 120" version="1.1" id="svg1" xml:space="preserve" inkscape:version="1.3.2 (1:1.3.2+202311252150+091e20ef0f)" sodipodi:docname="unsnap.svg" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview id="namedview1" pagecolor="#ffffff" bordercolor="#999999" borderopacity="1" inkscape:showpageshadow="2" inkscape:pageopacity="0" inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" inkscape:document-units="px" showguides="true" inkscape:zoom="4.5168066" inkscape:cx="51.695815" inkscape:cy="69.186048" inkscape:window-width="1920" inkscape:window-height="995" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" inkscape:current-layer="layer1"><sodipodi:guide position="315.49944,61.936443" orientation="0,-1" id="guide2" inkscape:locked="false" /></sodipodi:namedview><defs id="defs1" /><g inkscape:label="Layer 1" inkscape:groupmode="layer" id="layer1" transform="translate(-5,-5)"><path id="path1-2" style="fill:{color};fill-opacity:1;stroke-width:3.93092" d="m 91.670867,40.074491 c 0.948679,0.909931 1.380066,2.234124 1.148907,3.527969 l -3.447995,17.260743 c -0.381611,2.137177 -2.526217,4.001294 -4.081205,2.486091 L 79.257979,57.469775 64.262133,72.31877 c -1.383257,1.369713 -3.807955,0.909932 -4.298111,0.288099 -1.707154,-2.165786 -0.138139,-3.968458 0.177549,-4.304093 L 74.600311,52.930303 68.567707,47.05079 c -1.554665,-1.51554 0.253754,-3.707343 2.380393,-4.143751 l 17.16644,-3.89041 c 1.287478,-0.264342 2.622313,0.132882 3.556328,1.057866 z" sodipodi:nodetypes="cccccsssccccc" /><g id="g8" transform="matrix(-1,0,0,1,132.686,0)" /><g id="g10" transform="matrix(0.99755231,-0.06992414,-0.06992414,-0.99755231,14.642674,124.44485)"><path style="color:{color};fill:{color};stroke-linecap:round;-inkscape-stroke:none" d="M 10,90 45,10" id="path3" /><path id="path4" style="color:{color};fill:#808080;stroke-linecap:round;-inkscape-stroke:none;stroke:none;stroke-opacity:1;fill-opacity:1" d="M 45.097656,5.0019531 A 5,5 0 0 0 43.177734,5.34375 5,5 0 0 0 40.419922,7.9960938 L 35.865234,18.40625 c 3.405007,0.669609 6.469474,2.331825 8.867188,4.679688 L 49.580078,12.003906 A 5,5 0 0 0 47.003906,5.4199219 5,5 0 0 0 45.097656,5.0019531 Z M 22.177734,49.691406 5.4199219,87.996094 a 5,5 0 0 0 2.5761719,6.583984 5,5 0 0 0 6.5839842,-2.576172 L 31.621094,53.052734 c -3.513941,-0.175553 -6.76611,-1.396873 -9.44336,-3.361328 z" /><path style="fill:#808080;fill-opacity:1;stroke:{color};stroke-width:0;stroke-linecap:round;stroke-opacity:1" id="path9" sodipodi:type="arc" sodipodi:cx="32.616085" sodipodi:cy="35.55938" sodipodi:rx="12.741771" sodipodi:ry="12.741771" sodipodi:start="0" sodipodi:end="6.26046" sodipodi:open="true" sodipodi:arc-type="arc" d="M 45.357856,35.55938 A 12.741771,12.741771 0 0 1 32.688475,48.300945 12.741771,12.741771 0 0 1 19.875137,35.704157 12.741771,12.741771 0 0 1 32.398925,22.81946 12.741771,12.741771 0 0 1 45.354566,35.269844" /></g><path style="fill:{color};fill-opacity:1;stroke:{color};stroke-width:0;stroke-linecap:round;stroke-opacity:1" id="path10" sodipodi:type="arc" sodipodi:cx="-106.61823" sodipodi:cy="26.136267" sodipodi:rx="12.741771" sodipodi:ry="12.741771" sodipodi:start="0" sodipodi:end="6.26046" sodipodi:open="true" sodipodi:arc-type="arc" d="m -93.876462,26.136267 a 12.741771,12.741771 0 0 1 -12.669378,12.741565 12.741771,12.741771 0 0 1 -12.81334,-12.596788 12.741771,12.741771 0 0 1 12.52379,-12.884697 12.741771,12.741771 0 0 1 12.955638,12.450384" transform="scale(-1,1)" /></g></svg>