115 lines
3.7 KiB
JavaScript
115 lines
3.7 KiB
JavaScript
import L from 'leaflet';
|
|
import markerIcon2x from 'leaflet/dist/images/marker-icon-2x.png';
|
|
import markerIcon from 'leaflet/dist/images/marker-icon.png';
|
|
import markerShadow from 'leaflet/dist/images/marker-shadow.png';
|
|
|
|
delete L.Icon.Default.prototype._getIconUrl;
|
|
L.Icon.Default.mergeOptions({
|
|
iconUrl: markerIcon,
|
|
iconRetinaUrl: markerIcon2x,
|
|
shadowUrl: markerShadow,
|
|
});
|
|
|
|
function escHtml(str) {
|
|
return String(str ?? '')
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"');
|
|
}
|
|
|
|
const CLASSIFICATION_COLOURS = {
|
|
current: '#22c55e',
|
|
recent: '#64748b',
|
|
stale: '#f59e0b',
|
|
outdated: '#ef4444',
|
|
};
|
|
|
|
const UK_CENTRE = [54.0, -2.0];
|
|
const UK_ZOOM = 6;
|
|
|
|
export function stationMap(results) {
|
|
return {
|
|
results,
|
|
_map: null,
|
|
_markers: [],
|
|
|
|
init() {
|
|
this._map = L.map(this.$el, { zoomControl: true }).setView(UK_CENTRE, UK_ZOOM);
|
|
|
|
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
|
|
maxZoom: 19,
|
|
}).addTo(this._map);
|
|
|
|
if (this.results && this.results.length > 0) {
|
|
this._plotMarkers();
|
|
}
|
|
|
|
this.$watch('results', () => this._plotMarkers());
|
|
},
|
|
|
|
_clearMarkers() {
|
|
this._markers.forEach((m) => m.remove());
|
|
this._markers = [];
|
|
},
|
|
|
|
destroy() {
|
|
if (this._map) {
|
|
this._map.remove();
|
|
this._map = null;
|
|
this._markers = [];
|
|
}
|
|
},
|
|
|
|
_plotMarkers() {
|
|
if (!this._map) {
|
|
return;
|
|
}
|
|
this._clearMarkers();
|
|
|
|
if (!this.results || this.results.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const bounds = [];
|
|
|
|
this.results.forEach((station) => {
|
|
const colour = escHtml(CLASSIFICATION_COLOURS[station.price_classification] ?? '#64748b');
|
|
const miles = (station.distance_km * 0.621371).toFixed(1);
|
|
const supermarketTag = station.is_supermarket
|
|
? '<span style="display:inline-block;background:#84cc16;color:#fff;font-size:10px;padding:1px 5px;border-radius:3px;margin-left:4px;">Supermarket</span>'
|
|
: '';
|
|
|
|
const popup = `
|
|
<div style="min-width:160px">
|
|
<strong style="font-size:13px">${escHtml(station.name)}</strong>${supermarketTag}<br>
|
|
<span style="font-size:20px;font-weight:700;color:${colour}">${Number(station.price).toFixed(1)}p</span><br>
|
|
<span style="font-size:12px;color:#6b7280">${escHtml(miles)} miles away</span><br>
|
|
<span style="font-size:11px;color:#9ca3af">${escHtml(station.address)}, ${escHtml(station.postcode)}</span>
|
|
</div>
|
|
`;
|
|
|
|
const marker = L.circleMarker([station.lat, station.lng], {
|
|
radius: 9,
|
|
fillColor: colour,
|
|
color: '#ffffff',
|
|
weight: 2,
|
|
opacity: 1,
|
|
fillOpacity: 0.85,
|
|
}).bindPopup(popup);
|
|
|
|
marker.addTo(this._map);
|
|
this._markers.push(marker);
|
|
bounds.push([station.lat, station.lng]);
|
|
});
|
|
|
|
if (bounds.length === 1) {
|
|
this._map.setView(bounds[0], 14);
|
|
} else {
|
|
this._map.fitBounds(bounds, { padding: [40, 40], maxZoom: 14 });
|
|
}
|
|
},
|
|
};
|
|
}
|