|
|
|
@ -14,15 +14,10 @@ |
|
|
|
* limitations under the License. |
|
|
|
*/ |
|
|
|
|
|
|
|
import 'tooltipster/dist/css/tooltipster.bundle.min.css'; |
|
|
|
import 'tooltipster/dist/js/tooltipster.bundle.min.js'; |
|
|
|
import 'tooltipster/dist/css/plugins/tooltipster/sideTip/themes/tooltipster-sideTip-shadow.min.css'; |
|
|
|
import 'leaflet/dist/leaflet.css'; |
|
|
|
import L from 'leaflet/dist/leaflet'; |
|
|
|
|
|
|
|
import './image-map.scss'; |
|
|
|
|
|
|
|
const pinShape = '<path id="pin" d="m 12.033721,23.509909 c 0.165665,-3.220958 1.940547,-8.45243 4.512974,-11.745035 1.401507,-1.7940561 2.046337,-3.5425327 2.046337,-4.6032909 0,-3.6844827 -2.951858,-6.67149197 -6.592948,-6.67149197 l -1.68e-4,0 c -3.6412584,0 -6.5929483,2.98700927 -6.5929483,6.67149197 0,1.0607582 0.6448307,2.8092348 2.0463367,4.6032909 2.5724276,3.292605 4.3471416,8.524077 4.5129736,11.745035 l 0.06745,0 z" style="fill:#f2756a;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-opacity:1"/>'; |
|
|
|
const circleShape = '<circle id="circle" fill-rule="evenodd" cy="6.9234" cx="12" clip-rule="evenodd" r="1.5"/>'; |
|
|
|
const pinSvg = `<svg class="image-map-pin-image" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">${pinShape}${circleShape}</svg>`; |
|
|
|
const maxZoom = 4; |
|
|
|
|
|
|
|
export default class TbImageMap { |
|
|
|
|
|
|
|
@ -31,10 +26,9 @@ export default class TbImageMap { |
|
|
|
this.ctx = ctx; |
|
|
|
this.tooltips = []; |
|
|
|
|
|
|
|
$containerElement.append('<div id="image-map-container"><div id="image-map"></div></div>'); |
|
|
|
this.$containerElement = $containerElement; |
|
|
|
this.$containerElement.css('background', '#fff'); |
|
|
|
|
|
|
|
this.imageMapContainer = angular.element('#image-map-container', $containerElement); |
|
|
|
this.imageMap = angular.element('#image-map', $containerElement); |
|
|
|
this.aspect = 0; |
|
|
|
this.width = 0; |
|
|
|
this.height = 0; |
|
|
|
@ -108,7 +102,7 @@ export default class TbImageMap { |
|
|
|
if (keyData && keyData.data && keyData.data[0]) { |
|
|
|
var attrValue = keyData.data[0][1]; |
|
|
|
if (attrValue && attrValue.length) { |
|
|
|
this.loadImage(attrValue, this.aspect > 0 ? null : this.initCallback); |
|
|
|
this.loadImage(attrValue, this.aspect > 0 ? null : this.initCallback, true); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -117,72 +111,145 @@ export default class TbImageMap { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
loadImage(imageUrl, initCallback) { |
|
|
|
loadImage(imageUrl, initCallback, updateImage) { |
|
|
|
if (!imageUrl) { |
|
|
|
imageUrl = 'data:image/svg+xml;base64,PHN2ZyBpZD0ic3ZnMiIgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGhlaWdodD0iMTAwIiB3aWR0aD0iMTAwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgdmlld0JveD0iMCAwIDEwMCAxMDAiPgogPGcgaWQ9ImxheWVyMSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCAtOTUyLjM2KSI+CiAgPHJlY3QgaWQ9InJlY3Q0Njg0IiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBoZWlnaHQ9Ijk5LjAxIiB3aWR0aD0iOTkuMDEiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiB5PSI5NTIuODYiIHg9Ii40OTUwNSIgc3Ryb2tlLXdpZHRoPSIuOTkwMTAiIGZpbGw9IiNlZWUiLz4KICA8dGV4dCBpZD0idGV4dDQ2ODYiIHN0eWxlPSJ3b3JkLXNwYWNpbmc6MHB4O2xldHRlci1zcGFjaW5nOjBweDt0ZXh0LWFuY2hvcjptaWRkbGU7dGV4dC1hbGlnbjpjZW50ZXIiIGZvbnQtd2VpZ2h0PSJib2xkIiB4bWw6c3BhY2U9InByZXNlcnZlIiBmb250LXNpemU9IjEwcHgiIGxpbmUtaGVpZ2h0PSIxMjUlIiB5PSI5NzAuNzI4MDkiIHg9IjQ5LjM5NjQ3NyIgZm9udC1mYW1pbHk9IlJvYm90byIgZmlsbD0iIzY2NjY2NiI+PHRzcGFuIGlkPSJ0c3BhbjQ2OTAiIHg9IjUwLjY0NjQ3NyIgeT0iOTcwLjcyODA5Ij5JbWFnZSBiYWNrZ3JvdW5kIDwvdHNwYW4+PHRzcGFuIGlkPSJ0c3BhbjQ2OTIiIHg9IjQ5LjM5NjQ3NyIgeT0iOTgzLjIyODA5Ij5pcyBub3QgY29uZmlndXJlZDwvdHNwYW4+PC90ZXh0PgogIDxyZWN0IGlkPSJyZWN0NDY5NCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgaGVpZ2h0PSIxOS4zNiIgd2lkdGg9IjY5LjM2IiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgeT0iOTkyLjY4IiB4PSIxNS4zMiIgc3Ryb2tlLXdpZHRoPSIuNjM5ODYiIGZpbGw9Im5vbmUiLz4KIDwvZz4KPC9zdmc+Cg=='; |
|
|
|
} |
|
|
|
this.imageMap.css({backgroundImage: 'url('+imageUrl+')'}); |
|
|
|
this.imageUrl = imageUrl; |
|
|
|
var imageMap = this; |
|
|
|
var testImage = document.createElement('img'); // eslint-disable-line
|
|
|
|
testImage.style.visibility = 'hidden'; |
|
|
|
testImage.onload = function() { |
|
|
|
imageMap.aspect = testImage.width / testImage.height; |
|
|
|
document.body.removeChild(testImage); //eslint-disable-line
|
|
|
|
imageMap.onresize(); |
|
|
|
imageMap.onresize(updateImage); |
|
|
|
if (initCallback) { |
|
|
|
setTimeout(initCallback, 0); //eslint-disable-line
|
|
|
|
} else { |
|
|
|
imageMap.onresize(); |
|
|
|
} |
|
|
|
} |
|
|
|
document.body.appendChild(testImage); //eslint-disable-line
|
|
|
|
testImage.src = imageUrl; |
|
|
|
} |
|
|
|
|
|
|
|
onresize() { |
|
|
|
onresize(updateImage) { |
|
|
|
if (this.aspect > 0) { |
|
|
|
var width = this.imageMapContainer.width(); |
|
|
|
var width = this.$containerElement.width(); |
|
|
|
if (width > 0) { |
|
|
|
var height = width / this.aspect; |
|
|
|
var imageMapHeight = this.imageMapContainer.height(); |
|
|
|
var imageMapHeight = this.$containerElement.height(); |
|
|
|
if (imageMapHeight > 0 && height > imageMapHeight) { |
|
|
|
height = imageMapHeight; |
|
|
|
width = height * this.aspect; |
|
|
|
} |
|
|
|
width *= maxZoom; |
|
|
|
var prevWidth = this.width; |
|
|
|
var prevHeight = this.height; |
|
|
|
if (this.width !== width) { |
|
|
|
this.width = width; |
|
|
|
this.height = width / this.aspect; |
|
|
|
this.imageMap.css({width: this.width, height: this.height}); |
|
|
|
this.markers.forEach((marker) => { |
|
|
|
this.updateMarkerDimensions(marker); |
|
|
|
}); |
|
|
|
if (!this.map) { |
|
|
|
this.initMap(updateImage); |
|
|
|
} else { |
|
|
|
var lastCenterPos = this.latLngToPoint(this.map.getCenter()); |
|
|
|
lastCenterPos.x /= prevWidth; |
|
|
|
lastCenterPos.y /= prevHeight; |
|
|
|
this.updateBounds(updateImage, lastCenterPos); |
|
|
|
this.map.invalidateSize(true); |
|
|
|
this.updateMarkers(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
initMap(updateImage) { |
|
|
|
if (!this.map && this.aspect > 0) { |
|
|
|
var center = this.pointToLatLng(this.width/2, this.height/2); |
|
|
|
this.map = L.map(this.$containerElement[0], { |
|
|
|
minZoom: 1, |
|
|
|
maxZoom: maxZoom, |
|
|
|
center: center, |
|
|
|
zoom: 1, |
|
|
|
crs: L.CRS.Simple, |
|
|
|
attributionControl: false |
|
|
|
}); |
|
|
|
this.updateBounds(updateImage); |
|
|
|
this.updateMarkers(); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
pointToLatLng(x, y) { |
|
|
|
return L.CRS.Simple.pointToLatLng({x:x, y:y}, maxZoom-1); |
|
|
|
} |
|
|
|
|
|
|
|
latLngToPoint(latLng) { |
|
|
|
return L.CRS.Simple.latLngToPoint(latLng, maxZoom-1); |
|
|
|
} |
|
|
|
|
|
|
|
inited() { |
|
|
|
return this.aspect > 0 ? true : false; |
|
|
|
return angular.isDefined(this.map); |
|
|
|
} |
|
|
|
|
|
|
|
updateMarkerLabel(marker, settings) { |
|
|
|
if (settings.showLabel) { |
|
|
|
marker.labelElement.css({color: settings.labelColor}); |
|
|
|
marker.labelElement.html(`<b>${settings.labelText}</b>`); |
|
|
|
updateBounds(updateImage, lastCenterPos) { |
|
|
|
var w = this.width; |
|
|
|
var h = this.height; |
|
|
|
var southWest = this.pointToLatLng(0, h); |
|
|
|
var northEast = this.pointToLatLng(w, 0); |
|
|
|
var bounds = new L.LatLngBounds(southWest, northEast); |
|
|
|
|
|
|
|
if (updateImage && this.imageOverlay) { |
|
|
|
this.imageOverlay.remove(); |
|
|
|
this.imageOverlay = null; |
|
|
|
} |
|
|
|
|
|
|
|
if (this.imageOverlay) { |
|
|
|
this.imageOverlay.setBounds(bounds); |
|
|
|
} else { |
|
|
|
this.imageOverlay = L.imageOverlay(this.imageUrl, bounds).addTo(this.map); |
|
|
|
} |
|
|
|
var padding = 200 * maxZoom; |
|
|
|
southWest = this.pointToLatLng(-padding, h + padding); |
|
|
|
northEast = this.pointToLatLng(w+padding, -padding); |
|
|
|
var maxBounds = new L.LatLngBounds(southWest, northEast); |
|
|
|
this.map.setMaxBounds(maxBounds); |
|
|
|
if (lastCenterPos) { |
|
|
|
lastCenterPos.x *= w; |
|
|
|
lastCenterPos.y *= h; |
|
|
|
var center = this.pointToLatLng(lastCenterPos.x, lastCenterPos.y); |
|
|
|
this.ctx.$scope.$injector.get('$mdUtil').nextTick(() => { |
|
|
|
this.map.panTo(center, {animate: false}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
updateMarkerLabel(marker, settings) { |
|
|
|
marker.unbindTooltip(); |
|
|
|
marker.bindTooltip('<div style="color: '+ settings.labelColor +';"><b>'+settings.labelText+'</b></div>', |
|
|
|
{ className: 'tb-marker-label', permanent: true, direction: 'top', offset: marker.tooltipOffset }); |
|
|
|
} |
|
|
|
|
|
|
|
updateMarkerColor(marker, color) { |
|
|
|
marker.pinSvgElement.css({fill: color}); |
|
|
|
var pinColor = color.substr(1); |
|
|
|
var icon = L.icon({ |
|
|
|
iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + pinColor, |
|
|
|
iconSize: [21, 34], |
|
|
|
iconAnchor: [10, 34], |
|
|
|
popupAnchor: [0, -34], |
|
|
|
shadowUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_shadow', |
|
|
|
shadowSize: [40, 37], |
|
|
|
shadowAnchor: [12, 35] |
|
|
|
}); |
|
|
|
marker.setIcon(icon); |
|
|
|
} |
|
|
|
|
|
|
|
updateMarkerImage(marker, settings, image, maxSize) { |
|
|
|
var testImage = new Image(); // eslint-disable-line no-undef
|
|
|
|
var imageMap = this; |
|
|
|
var testImage = document.createElement('img'); // eslint-disable-line
|
|
|
|
testImage.style.visibility = 'hidden'; |
|
|
|
testImage.onload = function() { |
|
|
|
var width; |
|
|
|
var height; |
|
|
|
var aspect = testImage.width / testImage.height; |
|
|
|
document.body.removeChild(testImage); //eslint-disable-line
|
|
|
|
if (aspect > 1) { |
|
|
|
width = maxSize; |
|
|
|
height = maxSize / aspect; |
|
|
|
@ -190,74 +257,79 @@ export default class TbImageMap { |
|
|
|
width = maxSize * aspect; |
|
|
|
height = maxSize; |
|
|
|
} |
|
|
|
var size = Math.max(width, height); |
|
|
|
marker.size = size; |
|
|
|
if (marker.imgElement) { |
|
|
|
marker.imgElement.remove(); |
|
|
|
var icon = L.icon({ |
|
|
|
iconUrl: image, |
|
|
|
iconSize: [width, height], |
|
|
|
iconAnchor: [marker.offsetX * width, marker.offsetY * height], |
|
|
|
popupAnchor: [0, -height] |
|
|
|
}); |
|
|
|
marker.setIcon(icon); |
|
|
|
if (settings.showLabel) { |
|
|
|
marker.unbindTooltip(); |
|
|
|
marker.tooltipOffset = [0, -height * marker.offsetY + 10]; |
|
|
|
marker.bindTooltip('<div style="color: '+ settings.labelColor +';"><b>'+settings.labelText+'</b></div>', |
|
|
|
{ className: 'tb-marker-label', permanent: true, direction: 'top', offset: marker.tooltipOffset }); |
|
|
|
} |
|
|
|
marker.imgElement = angular.element(`<img src="${image}" aria-label="pin" class="image-map-pin-image"/>`); |
|
|
|
var left = (size - width)/2; |
|
|
|
var top = (size - height)/2; |
|
|
|
marker.imgElement.css({width: width, height: height, left: left, top: top}); |
|
|
|
marker.pinElement.append(marker.imgElement); |
|
|
|
imageMap.updateMarkerDimensions(marker); |
|
|
|
} |
|
|
|
document.body.appendChild(testImage); //eslint-disable-line
|
|
|
|
testImage.src = image; |
|
|
|
} |
|
|
|
|
|
|
|
updateMarkerDimensions(marker) { |
|
|
|
var pinElement = marker.pinElement; |
|
|
|
pinElement.css({width: marker.size, height: marker.size}); |
|
|
|
var left = marker.x * this.width - marker.size * marker.offsetX; |
|
|
|
var top = marker.y * this.height - marker.size * marker.offsetY; |
|
|
|
pinElement.css({left: left, top: top}); |
|
|
|
} |
|
|
|
|
|
|
|
createMarker(position, settings, onClickListener, markerArgs) { |
|
|
|
var marker = { |
|
|
|
size: 34, |
|
|
|
position: position |
|
|
|
}; |
|
|
|
var height = 34; |
|
|
|
var pinColor = settings.color.substr(1); |
|
|
|
var icon = L.icon({ |
|
|
|
iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + pinColor, |
|
|
|
iconSize: [21, 34], |
|
|
|
iconAnchor: [21 * settings.markerOffsetX, 34 * settings.markerOffsetY], |
|
|
|
popupAnchor: [0, -34], |
|
|
|
shadowUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_shadow', |
|
|
|
shadowSize: [40, 37], |
|
|
|
shadowAnchor: [12, 35] |
|
|
|
}); |
|
|
|
|
|
|
|
var pos = this.posFunction(position.x, position.y); |
|
|
|
marker.x = pos.x; |
|
|
|
marker.y = pos.y; |
|
|
|
var x = pos.x * this.width; |
|
|
|
var y = pos.y * this.height; |
|
|
|
var location = this.pointToLatLng(x, y); |
|
|
|
var marker = L.marker(location, {icon: icon}).addTo(this.map); |
|
|
|
marker.position = position; |
|
|
|
marker.offsetX = settings.markerOffsetX; |
|
|
|
marker.offsetY = settings.markerOffsetY; |
|
|
|
marker.pinElement = angular.element('<div class="image-map-pin"></div>'); |
|
|
|
|
|
|
|
if (settings.showLabel) { |
|
|
|
marker.labelElement = angular.element(`<div class="image-map-pin-title"><b>${settings.labelText}</b></div>`); |
|
|
|
marker.labelElement.css({color: settings.labelColor}); |
|
|
|
marker.pinElement.append(marker.labelElement); |
|
|
|
marker.tooltipOffset = [0, -height * marker.offsetY + 10]; |
|
|
|
marker.bindTooltip('<div style="color: '+ settings.labelColor +';"><b>'+settings.labelText+'</b></div>', |
|
|
|
{ className: 'tb-marker-label', permanent: true, direction: 'top', offset: marker.tooltipOffset }); |
|
|
|
} |
|
|
|
|
|
|
|
marker.imgElement = angular.element(pinSvg); |
|
|
|
marker.pinSvgElement = marker.imgElement.find('#pin'); |
|
|
|
marker.pinElement.append(marker.imgElement); |
|
|
|
|
|
|
|
marker.pinSvgElement.css({fill: settings.color}); |
|
|
|
|
|
|
|
this.updateMarkerDimensions(marker); |
|
|
|
|
|
|
|
this.imageMap.append(marker.pinElement); |
|
|
|
|
|
|
|
if (settings.useMarkerImage) { |
|
|
|
this.updateMarkerImage(marker, settings, settings.markerImage, settings.markerImageSize || 34); |
|
|
|
} |
|
|
|
|
|
|
|
if (settings.displayTooltip) { |
|
|
|
this.createTooltip(marker, settings.tooltipPattern, settings.tooltipReplaceInfo, markerArgs); |
|
|
|
this.createTooltip(marker, settings.tooltipPattern, settings.tooltipReplaceInfo, settings.autocloseTooltip, markerArgs); |
|
|
|
} |
|
|
|
|
|
|
|
if (onClickListener) { |
|
|
|
marker.pinElement.on('click', onClickListener); |
|
|
|
marker.on('click', onClickListener); |
|
|
|
} |
|
|
|
|
|
|
|
this.markers.push(marker); |
|
|
|
return marker; |
|
|
|
} |
|
|
|
|
|
|
|
updateMarkers() { |
|
|
|
this.markers.forEach((marker) => { |
|
|
|
this.updateMarkerLocation(marker); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
updateMarkerLocation(marker) { |
|
|
|
this.setMarkerPosition(marker, marker.position); |
|
|
|
} |
|
|
|
|
|
|
|
removeMarker(marker) { |
|
|
|
this.map.removeLayer(marker); |
|
|
|
var index = this.markers.indexOf(marker); |
|
|
|
if (index > -1) { |
|
|
|
marker.pinElement.remove(); |
|
|
|
@ -265,9 +337,10 @@ export default class TbImageMap { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
createTooltip(marker, pattern, replaceInfo, markerArgs) { |
|
|
|
var popup = new Popup(this.ctx, marker.pinElement); |
|
|
|
createTooltip(marker, pattern, replaceInfo, autoClose, markerArgs) { |
|
|
|
var popup = L.popup(); |
|
|
|
popup.setContent(''); |
|
|
|
marker.bindPopup(popup, {autoClose: autoClose, closeOnClick: false}); |
|
|
|
this.tooltips.push( { |
|
|
|
markerArgs: markerArgs, |
|
|
|
popup: popup, |
|
|
|
@ -302,9 +375,10 @@ export default class TbImageMap { |
|
|
|
setMarkerPosition(marker, position) { |
|
|
|
marker.position = position; |
|
|
|
var pos = this.posFunction(position.x, position.y); |
|
|
|
marker.x = pos.x; |
|
|
|
marker.y = pos.y; |
|
|
|
this.updateMarkerDimensions(marker); |
|
|
|
var x = pos.x * this.width; |
|
|
|
var y = pos.y * this.height; |
|
|
|
var location = this.pointToLatLng(x, y); |
|
|
|
marker.setLatLng(location); |
|
|
|
} |
|
|
|
|
|
|
|
getPolylineLatLngs(/*polyline*/) { |
|
|
|
@ -340,38 +414,3 @@ class Position { |
|
|
|
return loc && loc.x == this.x && loc.y == this.y; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
class Popup { |
|
|
|
constructor(ctx, anchor) { |
|
|
|
anchor.tooltipster( |
|
|
|
{ |
|
|
|
theme: 'tooltipster-shadow', |
|
|
|
delay: 100, |
|
|
|
trigger: 'custom', |
|
|
|
triggerOpen: { |
|
|
|
click: true, |
|
|
|
tap: true |
|
|
|
}, |
|
|
|
trackOrigin: true |
|
|
|
} |
|
|
|
); |
|
|
|
this.tooltip = anchor.tooltipster('instance'); |
|
|
|
var contentElement = angular.element('<div class="image-map-pin-tooltip">' + |
|
|
|
'<a class="image-map-pin-tooltip-close-button" id="close" style="outline: none;">×</a>' + |
|
|
|
'<div id="tooltip-content">' + |
|
|
|
'</div>' + |
|
|
|
'</div>'); |
|
|
|
var $compile = ctx.$scope.$injector.get('$compile'); |
|
|
|
$compile(contentElement)(ctx.$scope); |
|
|
|
var popup = this; |
|
|
|
contentElement.find('#close').on('click', function() { |
|
|
|
popup.tooltip.close(); |
|
|
|
}); |
|
|
|
this.content = contentElement.find('#tooltip-content'); |
|
|
|
this.tooltip.content(contentElement); |
|
|
|
} |
|
|
|
|
|
|
|
setContent(content) { |
|
|
|
this.content.html(content); |
|
|
|
} |
|
|
|
} |
|
|
|
|