340 lines
8.8 KiB
JavaScript
340 lines
8.8 KiB
JavaScript
/*
|
|
* Leaflet Location Picker v0.3.4 - 2022-11-18
|
|
*
|
|
* Copyright 2022 Stefano Cudini
|
|
* stefano.cudini@gmail.com
|
|
* https://opengeo.tech/
|
|
*
|
|
* Licensed under the MIT license.
|
|
*
|
|
* Demo:
|
|
* https://opengeo.tech/maps/leaflet-locationpicker/
|
|
*
|
|
* Source:
|
|
* git@github.com:stefanocudini/leaflet-locationpicker.git
|
|
*
|
|
*/
|
|
|
|
(function (factory) {
|
|
if(typeof define === 'function' && define.amd) {
|
|
//AMD
|
|
define(['jquery','leaflet'], factory);
|
|
} else if(typeof module !== 'undefined') {
|
|
// Node/CommonJS
|
|
module.exports = factory(require('jquery','leaflet'));
|
|
} else {
|
|
// Browser globals
|
|
if(typeof window.jQuery === 'undefined')
|
|
throw 'jQuery must be loaded first';
|
|
if(typeof window.L === 'undefined')
|
|
throw 'Leaflet must be loaded first';
|
|
factory(window.jQuery, window.L);
|
|
}
|
|
})(function(jQuery, L) {
|
|
|
|
var $ = jQuery;
|
|
|
|
$.fn.leafletLocationPicker = function(opts, onChangeLocation) {
|
|
|
|
var http = window.location.protocol;
|
|
|
|
var baseClassName = 'leaflet-locpicker',
|
|
baseLayers = {
|
|
'OSM': http + '//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
|
|
// 'SAT': http + '//otile1.mqcdn.com/tiles/1.0.0/sat/{z}/{x}/{y}.png' // AK 2021-04-22: invalid URL!!
|
|
//TODO add more free base layers
|
|
};
|
|
|
|
var optsMap = {
|
|
zoom: 0,
|
|
center: L.latLng([40,0]),
|
|
zoomControl: false,
|
|
attributionControl: false
|
|
};
|
|
|
|
if($.isPlainObject(opts) && $.isPlainObject(opts.map))
|
|
optsMap = $.extend(optsMap, opts.map);
|
|
|
|
var defaults = {
|
|
alwaysOpen: false,
|
|
className: baseClassName,
|
|
location: optsMap.center,
|
|
locationFormat: '{lat}{sep}{lng}',
|
|
locationMarker: true,
|
|
locationDigits: 6,
|
|
locationSep: ',',
|
|
position: 'topright',
|
|
layer: 'OSM',
|
|
height: 140,
|
|
width: 200,
|
|
event: 'click',
|
|
cursorSize: '30px',
|
|
readOnly: false,
|
|
map: optsMap,
|
|
onChangeLocation: $.noop,
|
|
mapContainer: ""
|
|
};
|
|
|
|
if($.isPlainObject(opts))
|
|
opts = $.extend(defaults, opts);
|
|
|
|
else if($.isFunction(opts))
|
|
opts = $.extend(defaults, {
|
|
onChangeLocation: opts
|
|
});
|
|
else
|
|
opts = defaults;
|
|
|
|
if($.isFunction(onChangeLocation))
|
|
opts = $.extend(defaults, {
|
|
onChangeLocation: onChangeLocation
|
|
});
|
|
|
|
function roundLocation(loc) {
|
|
return loc ? L.latLng(
|
|
parseFloat(loc.lat).toFixed(opts.locationDigits),
|
|
parseFloat(loc.lng).toFixed(opts.locationDigits)
|
|
) : loc;
|
|
}
|
|
|
|
function parseLocation(loc) {
|
|
var retLoc = loc;
|
|
|
|
switch($.type(loc)) {
|
|
case 'string':
|
|
var ll = loc.split(opts.locationSep);
|
|
if(ll[0] && ll[1])
|
|
retLoc = L.latLng(ll);
|
|
else
|
|
retLoc = null;
|
|
break;
|
|
case 'array':
|
|
retLoc = L.latLng(loc);
|
|
break;
|
|
case 'object':
|
|
var lat, lng;
|
|
if(loc.hasOwnProperty('lat'))
|
|
lat = loc.lat;
|
|
else if(loc.hasOwnProperty('latitude'))
|
|
lat = loc.latitude;
|
|
|
|
if(loc.hasOwnProperty('lng'))
|
|
lng = loc.lng;
|
|
else if(loc.hasOwnProperty('lon'))
|
|
lng = loc.lon;
|
|
else if(loc.hasOwnProperty('longitude'))
|
|
lng = loc.longitude;
|
|
|
|
retLoc = L.latLng(parseFloat(lat),parseFloat(lng));
|
|
break;
|
|
default:
|
|
retLoc = loc;
|
|
}
|
|
return roundLocation( retLoc );
|
|
}
|
|
|
|
function buildMap(self) {
|
|
|
|
self.divMap = document.createElement('div');
|
|
self.$map = $(document.createElement('div'))
|
|
.addClass(opts.className + '-map')
|
|
.height(opts.height)
|
|
.width(opts.width)
|
|
.append(self.divMap);
|
|
|
|
if (opts.readOnly)
|
|
self.$map.addClass("read-only");
|
|
|
|
//adds either as global div or specified container
|
|
//if added to specified container add some style class
|
|
if(opts.mapContainer && $(opts.mapContainer))
|
|
self.$map.appendTo(opts.mapContainer)
|
|
.addClass('map-select');
|
|
else
|
|
self.$map.appendTo('body');
|
|
|
|
if(self.location)
|
|
opts.map.center = self.location;
|
|
|
|
if(typeof opts.layer === 'string' && baseLayers[opts.layer]) {
|
|
opts.map.layers = L.tileLayer(baseLayers[opts.layer]);
|
|
|
|
}else if (opts.layer instanceof L.TileLayer ||
|
|
opts.layer instanceof L.GridLayer ||
|
|
opts.layer instanceof L.LayerGroup) {
|
|
opts.map.layers = opts.layer;
|
|
|
|
}else {
|
|
opts.map.layers = L.tileLayer(baseLayers.OSM);
|
|
}
|
|
|
|
//leaflet map
|
|
self.map = L.map(self.divMap, opts.map)
|
|
.addControl( L.control.zoom({position: 'bottomright'}) )
|
|
.on(opts.event, function(e) {
|
|
if (!opts.readOnly)
|
|
self.setLocation(e.latlng);
|
|
});
|
|
|
|
if(opts.activeOnMove) {
|
|
self.map.on('move', function(e) {
|
|
self.setLocation(e.target.getCenter());
|
|
});
|
|
}
|
|
|
|
//only adds closeBtn if not alwaysOpen
|
|
if(opts.alwaysOpen!==true){
|
|
var xmap = L.control({position: 'topright'});
|
|
xmap.onAdd = function(map) {
|
|
var btn_holder = L.DomUtil.create('div', 'leaflet-bar');
|
|
var btn = L.DomUtil.create('a','leaflet-control '+opts.className+'-close');
|
|
btn.innerHTML = '×';
|
|
btn_holder.appendChild(btn);
|
|
L.DomEvent
|
|
.on(btn, 'click', L.DomEvent.stop, self)
|
|
.on(btn, 'click', self.closeMap, self);
|
|
return btn_holder;
|
|
};
|
|
xmap.addTo(self.map);
|
|
}
|
|
|
|
if(opts.locationMarker)
|
|
self.marker = buildMarker(self.location).addTo(self.map);
|
|
|
|
return self.$map;
|
|
}
|
|
|
|
function buildMarker(loc) {
|
|
return L.marker(parseLocation(loc) || L.latLng(0,0), {
|
|
icon: L.divIcon({
|
|
className: opts.className+'-marker',
|
|
iconAnchor: L.point(0, 0),
|
|
|
|
// TODO: get rid of inline CSS completely, in order to make it compliant with Content-Security-Policy that doesn't wallows 'unsafe-inline' CSS.
|
|
// AK: These additional styles can be set up with JavaScript, after creation of the marker icon element.
|
|
html: '<div' + ("30px" !== opts.cursorSize ? 'style="width: ' + opts.cursorSize + '; height: ' + opts.cursorSize + ';"' : '') + '>'+
|
|
'<div class="corner1"></div>'+
|
|
'<div class="corner2"></div>'+
|
|
'<div class="corner3"></div>'+
|
|
'<div class="corner4"></div>'+
|
|
'</div>'
|
|
})
|
|
});
|
|
}
|
|
|
|
$(this).each(function(index, input) {
|
|
var self = this;
|
|
|
|
self.$input = $(this);
|
|
|
|
self.options = opts; // access to options
|
|
self.setReadOnly = function(newReadOnly) {
|
|
opts.readOnly = newReadOnly;
|
|
self.$map.toggleClass("read-only", newReadOnly);
|
|
};
|
|
|
|
self.locationOri = self.$input.val();
|
|
|
|
self.onChangeLocation = function() {
|
|
var edata = {
|
|
latlng: self.location,
|
|
location: self.getLocation()
|
|
};
|
|
self.$input.trigger($.extend(edata, {
|
|
type: 'changeLocation'
|
|
}));
|
|
opts.onChangeLocation.call(self, edata);
|
|
};
|
|
|
|
self.setLocation = function(loc, noSet) {
|
|
loc = loc || defaults.location;
|
|
self.location = parseLocation(loc);
|
|
|
|
if(self.marker)
|
|
self.marker.setLatLng(loc);
|
|
|
|
if(!noSet) {
|
|
self.$input.data('location', self.location);
|
|
self.$input.val( self.getLocation() );
|
|
self.onChangeLocation();
|
|
}
|
|
};
|
|
|
|
self.getLocation = function() {
|
|
return self.location ? L.Util.template(opts.locationFormat, {
|
|
lat: self.location.lat,
|
|
lng: self.location.lng,
|
|
sep: opts.locationSep
|
|
}) : self.location;
|
|
};
|
|
|
|
self.updatePosition = function() {
|
|
switch(opts.position) {
|
|
case 'bottomleft':
|
|
self.$map.css({
|
|
top: self.$input.offset().top + self.$input.height() + 6,
|
|
left: self.$input.offset().left
|
|
});
|
|
break;
|
|
case 'topright':
|
|
self.$map.css({
|
|
top: self.$input.offset().top,
|
|
left: self.$input.offset().left + self.$input.width() + 5
|
|
});
|
|
break;
|
|
}
|
|
};
|
|
|
|
self.openMap = function() {
|
|
self.updatePosition();
|
|
self.$map.show();
|
|
self.map.invalidateSize();
|
|
self.$input.trigger('show');
|
|
};
|
|
|
|
self.closeMap = function() {
|
|
self.$map.hide();
|
|
self.$input.trigger('hide');
|
|
};
|
|
|
|
self.setLocation(self.locationOri, true);
|
|
|
|
self.$map = buildMap(self);
|
|
|
|
self.$input
|
|
.addClass(opts.className)
|
|
.on('focus.'+opts.className, function(e) {
|
|
e.preventDefault();
|
|
self.openMap();
|
|
})
|
|
.on('blur.'+opts.className, function(e) {
|
|
e.preventDefault();
|
|
var p = e.relatedTarget;
|
|
var close = true;
|
|
while (p) {
|
|
if (p._leaflet) {
|
|
close = false;
|
|
break;
|
|
}
|
|
p = p.parentElement;
|
|
}
|
|
if(close) {
|
|
setTimeout(function() {
|
|
self.closeMap();
|
|
}, 100);
|
|
}
|
|
});
|
|
|
|
$(window).on('resize', function() {
|
|
if (self.$map.is(':visible'))
|
|
self.updatePosition();
|
|
});
|
|
//opens map initially if alwaysOpen
|
|
if(opts.alwaysOpen && opts.alwaysOpen===true) self.openMap();
|
|
});
|
|
|
|
return this;
|
|
};
|
|
|
|
});
|