2023-01-25 18:41:55 +00:00
< template >
2023-02-08 22:13:13 +00:00
< div class = "map-matter" >
2023-02-09 05:55:12 +00:00
< LMap
2023-02-08 21:35:42 +00:00
class = "map"
2023-01-26 04:51:42 +00:00
ref = "map"
2023-02-09 20:02:11 +00:00
: crossOrigin = "true"
2023-02-08 21:35:42 +00:00
: zoom = "zoom"
: minZoom = "2"
@ moveend = "refresh"
@ zoomend = "refresh"
2023-01-26 04:51:42 +00:00
>
2023-02-09 17:51:26 +00:00
< LTileLayer :url ="tileurl" :attribution ="attribution" / >
2023-02-09 05:55:12 +00:00
< LMarker
2023-02-08 03:59:04 +00:00
v - for = "cluster in clusters"
2023-02-09 07:55:54 +00:00
: key = "cluster.id"
2023-02-08 03:59:04 +00:00
: lat - lng = "cluster.center"
2023-02-09 07:15:48 +00:00
@ click = "zoomTo(cluster.center)"
2023-02-08 03:59:04 +00:00
>
2023-02-09 07:55:54 +00:00
< LIcon : icon -anchor = " [ 24 , 24 ] " >
2023-02-09 05:55:12 +00:00
< div class = "preview" >
2023-02-09 09:01:15 +00:00
< div class = "count" v-if ="cluster.count > 1" >
{ { cluster . count } }
< / div >
2023-02-09 05:55:12 +00:00
< img :src ="clusterPreviewUrl(cluster)" / >
< / div >
< / LIcon >
< / LMarker >
< / LMap >
2023-01-26 04:51:42 +00:00
< / div >
2023-01-25 18:41:55 +00:00
< / template >
< script lang = "ts" >
2023-02-08 21:35:42 +00:00
import { defineComponent } from "vue" ;
2023-02-09 05:55:12 +00:00
import { LMap , LTileLayer , LMarker , LPopup , LIcon } from "vue2-leaflet" ;
2023-02-08 21:35:42 +00:00
2023-02-08 03:59:04 +00:00
import { API } from "../../services/API" ;
2023-02-09 16:29:53 +00:00
import axios from "@nextcloud/axios" ;
2023-01-25 18:41:55 +00:00
2023-02-08 21:35:42 +00:00
import "leaflet/dist/leaflet.css" ;
2023-01-25 18:41:55 +00:00
2023-02-09 17:51:26 +00:00
const OSM _TILE _URL = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" ;
const OSM _ATTRIBUTION =
2023-02-08 21:35:42 +00:00
'© <a target="_blank" href="http://osm.org/copyright">OpenStreetMap</a> contributors' ;
2023-02-09 17:51:26 +00:00
const STAMEN _URL = ` https://stamen-tiles-{s}.a.ssl.fastly.net/terrain-background/{z}/{x}/{y}{r}.png ` ;
const STAMEN _ATTRIBUTION = ` Map tiles by <a href="http://stamen.com">Stamen Design</a>, under <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. Data by <a href="http://openstreetmap.org">OpenStreetMap</a>, under <a href="http://www.openstreetmap.org/copyright">ODbL</a>. ` ;
2023-01-25 18:41:55 +00:00
2023-02-08 21:53:38 +00:00
type IMarkerCluster = {
2023-02-09 05:55:12 +00:00
id ? : number ;
2023-02-09 16:25:37 +00:00
u ? : any ;
2023-02-08 21:53:38 +00:00
center : [ number , number ] ;
count : number ;
} ;
2023-01-25 18:41:55 +00:00
export default defineComponent ( {
2023-02-08 22:13:13 +00:00
name : "MapSplitMatter" ,
2023-01-26 04:51:42 +00:00
components : {
LMap ,
LTileLayer ,
LMarker ,
LPopup ,
2023-02-09 05:55:12 +00:00
LIcon ,
2023-01-26 04:51:42 +00:00
} ,
data : ( ) => ( {
2023-02-08 21:35:42 +00:00
zoom : 2 ,
clusters : [ ] as IMarkerCluster [ ] ,
2023-01-26 04:51:42 +00:00
} ) ,
mounted ( ) {
2023-02-08 21:35:42 +00:00
const map = this . $refs . map as LMap ;
2023-01-25 18:41:55 +00:00
2023-02-08 21:35:42 +00:00
// Make sure the zoom control doesn't overlap with the navbar
map . mapObject . zoomControl . setPosition ( "topright" ) ;
// Initialize
this . refresh ( ) ;
2023-01-26 04:51:42 +00:00
} ,
2023-01-25 18:41:55 +00:00
2023-02-09 17:51:26 +00:00
computed : {
tileurl ( ) {
return this . zoom >= 5 ? OSM _TILE _URL : STAMEN _URL ;
} ,
attribution ( ) {
return this . zoom >= 5 ? OSM _ATTRIBUTION : STAMEN _ATTRIBUTION ;
} ,
} ,
2023-01-26 04:51:42 +00:00
methods : {
2023-02-08 21:35:42 +00:00
async refresh ( ) {
const map = this . $refs . map as LMap ;
// Get boundaries of the map
const boundary = map . mapObject . getBounds ( ) ;
const minLat = boundary . getSouth ( ) ;
const maxLat = boundary . getNorth ( ) ;
const minLon = boundary . getWest ( ) ;
const maxLon = boundary . getEast ( ) ;
// Set query parameters to route if required
const s = ( x : number ) => x . toFixed ( 6 ) ;
const bounds = ` ${ s ( minLat ) } , ${ s ( maxLat ) } , ${ s ( minLon ) } , ${ s ( maxLon ) } ` ;
2023-02-09 17:51:26 +00:00
this . zoom = map . mapObject . getZoom ( ) ;
const zoom = this . zoom . toString ( ) ;
2023-02-08 21:35:42 +00:00
if ( this . $route . query . b === bounds && this . $route . query . z === zoom ) {
return ;
}
this . $router . replace ( { query : { b : bounds , z : zoom } } ) ;
2023-02-08 18:52:53 +00:00
// Show clusters correctly while draging the map
2023-02-08 21:35:42 +00:00
const query = new URLSearchParams ( ) ;
2023-02-08 21:53:38 +00:00
query . set ( "bounds" , bounds ) ;
2023-02-08 21:35:42 +00:00
query . set ( "zoom" , zoom ) ;
2023-02-08 03:59:04 +00:00
2023-02-08 21:35:42 +00:00
// Make API call
2023-02-08 22:13:13 +00:00
const url = API . Q ( API . MAP _CLUSTERS ( ) , query ) ;
2023-02-08 03:59:04 +00:00
const res = await axios . get ( url ) ;
this . clusters = res . data ;
2023-01-25 18:41:55 +00:00
} ,
2023-02-09 05:55:12 +00:00
clusterPreviewUrl ( cluster : IMarkerCluster ) {
2023-02-09 16:25:37 +00:00
let url = API . MAP _CLUSTER _PREVIEW ( cluster . id ) ;
if ( cluster . u ) {
url += ` ?u= ${ cluster . u } ` ;
}
return url ;
2023-02-09 05:55:12 +00:00
} ,
2023-02-09 07:15:48 +00:00
zoomTo ( center : [ number , number ] ) {
const map = this . $refs . map as LMap ;
2023-02-09 07:36:31 +00:00
const zoom = map . mapObject . getZoom ( ) + 2 ;
2023-02-09 07:15:48 +00:00
map . mapObject . setView ( center , zoom , { animate : true } ) ;
} ,
2023-01-26 04:51:42 +00:00
} ,
2023-01-25 18:41:55 +00:00
} ) ;
< / script >
< style lang = "scss" scoped >
2023-02-08 22:13:13 +00:00
. map - matter {
2023-02-08 21:35:42 +00:00
height : 100 % ;
width : 100 % ;
}
. map {
height : 100 % ;
width : 100 % ;
margin : 0 ;
z - index : 0 ;
2023-01-25 18:41:55 +00:00
}
2023-02-09 05:55:12 +00:00
. preview {
width : 48 px ;
height : 48 px ;
2023-02-09 16:25:37 +00:00
background - color : rgba ( 0 , 0 , 0 , 0.3 ) ;
2023-02-09 05:55:12 +00:00
border - radius : 5 px ;
position : relative ;
transition : transform 0.2 s ;
& : hover {
transform : scale ( 1.8 ) ;
}
img {
width : 100 % ;
height : 100 % ;
object - fit : cover ;
border - radius : 5 px ;
2023-02-09 07:15:48 +00:00
cursor : pointer ;
2023-02-09 05:55:12 +00:00
}
. count {
position : absolute ;
top : 0 ;
right : 0 ;
background - color : var ( -- color - primary - default ) ;
color : var ( -- color - primary - text ) ;
padding : 0 4 px ;
border - radius : 5 px ;
font - size : 0.8 em ;
}
}
< / style >
< style lang = "scss" >
2023-02-09 07:55:54 +00:00
. leaflet - marker - icon {
transition : transform 0.2 s ;
animation : fade - in 0.2 s ;
}
2023-02-09 05:55:12 +00:00
// Show leaflet marker on top on hover
. leaflet - marker - icon : hover {
z - index : 100000 ! important ;
}
2023-02-09 07:55:54 +00:00
2023-02-09 17:17:24 +00:00
// Dark mode
$darkFilter : invert ( 1 ) grayscale ( 1 ) brightness ( 1.3 ) contrast ( 1.3 ) ;
. leaflet - tile - pane {
body [ data - theme - dark ] & ,
body [ data - theme - dark - highcontrast ] & {
filter : $darkFilter ;
}
@ media ( prefers - color - scheme : dark ) {
body [ data - theme - default ] & {
filter : $darkFilter ;
}
}
}
2023-02-09 07:55:54 +00:00
@ keyframes fade - in {
0 % {
opacity : 0 ;
}
100 % {
opacity : 1 ;
}
}
2023-01-26 04:51:42 +00:00
< / style >