Skip to content
Snippets Groups Projects
Commit d48b77be authored by xaralis's avatar xaralis
Browse files

feat(maps_utils): improve UX and allow muting individual features

parent 8952fa67
Branches
No related tags found
2 merge requests!529Release,!528Random fixes/improvements
......@@ -74,6 +74,7 @@
data-display-zoom-control="false"
data-display-legend="false"
data-display-popups="false"
data-handle-clicks="false"
data-initial-zoom="{{ page.initial_zoom }}"
data-tile-server-config="{{ js_map.tile_server_config }}"
data-tile-style="{{ js_map.style }}"
......
......@@ -126,6 +126,10 @@ const GeoFeatureCollection = Vue.component("GeoFeatureCollection", {
type: Boolean,
default: true,
},
handleClicks: {
type: Boolean,
default: true,
},
height: {
type: String,
default: "50rem",
......@@ -204,9 +208,16 @@ const GeoFeatureCollection = Vue.component("GeoFeatureCollection", {
},
methods: {
initialize(categories, geojson) {
// Annotate with proper id, slug and composed url.
// Annotate with proper composed url and a stash of markers.
const annotatedFeatures = geojson.features.map((f) => {
// Identifier in URL.
f.properties.url = `${f.id}-${f.slug}`;
// Muted property to control opacity.
f.properties.muted = false;
// Marker stash.
f.properties.markers = [];
// Layer link.
f.properties.layer = null;
return f;
});
......@@ -280,10 +291,10 @@ const GeoFeatureCollection = Vue.component("GeoFeatureCollection", {
// Get style for given feature.
const style = (feature) => ({
fillColor: colorForFeature(feature),
weight: 2,
opacity: 0.7,
weight: 3,
opacity: 0.8,
color: colorForFeature(feature),
fillOpacity: 0.5,
fillOpacity: 0.6,
});
const markerIconForCategory = (categoryName, number) =>
......@@ -314,6 +325,9 @@ const GeoFeatureCollection = Vue.component("GeoFeatureCollection", {
offset: [0, -64],
});
// Add marker to feature marker list.
feature.properties.markers.push(featureMarker);
// Add item marker to the cluster.
markers.addLayer(featureMarker);
......@@ -326,7 +340,9 @@ const GeoFeatureCollection = Vue.component("GeoFeatureCollection", {
*/
const pointToLayer = (feature, latlng) => {
const onClick = (evt) => {
if (this.handleClicks) {
this.zoomToPoint(evt.latlng, feature);
}
};
return addMarker(feature, latlng, onClick);
};
......@@ -337,7 +353,11 @@ const GeoFeatureCollection = Vue.component("GeoFeatureCollection", {
*/
const onEachFeature = (feature, layer) => {
const markerPosLatLng = [];
const onClick = (evt) => this.zoomToLayer(layer, true);
const onClick = (evt) => {
if (this.handleClicks) {
this.zoomToLayer(layer, true);
}
}
const markerForPolyCoords = (coords) => {
// Find pole of inaccessibility (not centroid) for the polygon
......@@ -371,14 +391,18 @@ const GeoFeatureCollection = Vue.component("GeoFeatureCollection", {
**/
if (feature.geometry.type == "Polygon") {
markerForPolyCoords(feature.geometry.coordinates);
feature.properties.layer = layer;
} else if (feature.geometry.type == "MultiPolygon") {
feature.geometry.coordinates.forEach(markerForPolyCoords);
feature.properties.layer = layer;
} else if (feature.geometry.type == "LineString") {
markerForLineStringCoords(feature.geometry.coordinates);
feature.properties.layer = layer;
} else if (feature.geometry.type == "MultiLineString") {
feature.geometry.coordinates.forEach(
markerForLineStringCoords
);
feature.properties.layer = layer;
} else if (feature.geometry.type == "MultiPoint") {
// Supported via `pointToLayer`, noop here.
} else {
......@@ -390,10 +414,13 @@ const GeoFeatureCollection = Vue.component("GeoFeatureCollection", {
if (markerPosLatLng.length) {
markerPosLatLng.forEach((pos) => {
addMarker(feature, pos, onClick);
if (this.handleClicks) {
// Bind click event on the layer.
layer.on({
click: (evt) => this.zoomToLayer(evt.target, true),
});
}
});
}
};
......@@ -453,7 +480,7 @@ const GeoFeatureCollection = Vue.component("GeoFeatureCollection", {
// If query is present when starting, locate the item and zoom to it.
if (urlParams.has("item")) {
this.zoomToFeature(urlParams.get("item"), false);
this.zoomToFeature(urlParams.get("item"), false, true);
} else if (this.currentItem) {
this.closeItemInfo(false);
}
......@@ -478,15 +505,43 @@ const GeoFeatureCollection = Vue.component("GeoFeatureCollection", {
toggleExpandCategory(category) {
category.expanded = !category.expanded;
},
/**
* Toggle muted state for a feature.
* When feature is muted, it will be less opaque in the list. Used for
* better orientation when there are lots of features.
* @param {Object} feature
*/
toggleMuted(feature) {
feature.properties.muted = !feature.properties.muted;
feature.properties.markers.forEach((marker) => {
marker.setOpacity(feature.properties.muted ? 0.2 : 1);
});
if (feature.properties.layer) {
feature.properties.layer.setStyle(feature.properties.muted ? {
opacity: 0.3,
fillOpacity: 0.2,
} : {
opacity: 0.8,
fillOpacity: 0.6,
});
}
},
/**
* Zoom to a point.
*
* @param {L.Latlng} Latlng
* @param {Object} feature
* @param {Boolean} updateUrl whether to push new state to history.
* @param {Boolean} zoom whether to zoom to the item
*/
zoomToPoint(latlng, feature, updateUrl = true) {
zoomToPoint(latlng, feature, updateUrl = true, zoom = false) {
if (zoom) {
this.map.flyTo(latlng, 18);
} else {
this.map.panTo(latlng);
}
this.currentItem = feature.properties;
if (updateUrl) {
......@@ -498,9 +553,15 @@ const GeoFeatureCollection = Vue.component("GeoFeatureCollection", {
*
* @param {L.Layer} layer
* @param {Boolean} updateUrl whether to push new state to history.
* @param {Boolean} zoom whether to zoom to the items
*/
zoomToLayer(layer, updateUrl = true) {
this.map.fitBounds(layer.getBounds());
zoomToLayer(layer, updateUrl = true, zoom = false) {
if (zoom) {
this.map.flyToBounds(layer.getBounds());
} else {
this.map.panInsideBounds(layer.getBounds());
}
this.currentItem = layer.feature.properties;
if (updateUrl) {
......@@ -513,13 +574,18 @@ const GeoFeatureCollection = Vue.component("GeoFeatureCollection", {
*
* @param {String} slugUrl
* @param {Boolean} updateUrl whether to push new state to history.
* @param {Boolean} zoom whether to zoom to the items
*/
zoomToFeature(slugUrl, updateUrl = true) {
zoomToFeature(slugUrl, updateUrl = true, zoom = false) {
const layer = Object.values(this.layer._layers).find(
(l) => l.feature.properties.slug == slugUrl
);
if (layer) {
this.zoomToLayer(layer, updateUrl);
if (layer.feature.geometry.type == "Point") {
this.zoomToPoint(layer._latlng, layer.feature, updateUrl, zoom);
} else {
this.zoomToLayer(layer, updateUrl, zoom);
}
}
},
/**
......@@ -552,10 +618,10 @@ const GeoFeatureCollection = Vue.component("GeoFeatureCollection", {
<button class="head-allcaps-4xs cursor-pointer" @click="toggleExpandCategory(category)">{{ category.name }} ({{ category.items.length }})</button>
</div>
<ul v-show="category.expanded" :class="{'mb-2': index != categoryCount - 1}">
<li v-for="item in category.items" :key="item.properties.title">
<button @click="zoomToFeature(item.properties.slug)" class="text-left leading-tight text-xs" style="max-width: 17em;">
<span v-if="item.properties.index" class="rounded-full inline-flex items-center justify-center bg-grey-125 font-bold text-center text-2xs w-4 h-4 mr-1">{{ item.properties.index }}</span>
<span>{{ item.properties.title }}</span>
<li v-for="feature in category.items" :key="feature.properties.title">
<button @click="toggleMuted(feature)" class="text-left leading-tight text-xs" style="max-width: 17em;" :class="{'opacity-50': feature.properties.muted}">
<span v-if="feature.properties.index" class="rounded-full inline-flex items-center justify-center bg-grey-125 font-bold text-center text-2xs w-4 h-4 mr-1">{{ feature.properties.index }}</span>
<span>{{ feature.properties.title }}</span>
</button>
</li>
</ul>
......@@ -610,6 +676,7 @@ Array.from(document.getElementsByClassName("v-geo-feature-collection")).forEach(
displayZoomControl:
el.dataset.displayZoomControl != "false",
displayPopups: el.dataset.displayPopups != "false",
handleClicks: el.dataset.handleClicks != "false",
height: el.dataset.height,
tileStyle: el.dataset.tileStyle,
initialZoom: parseInt(el.dataset.initialZoom),
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment