function OmekaMap(mapDivId, center, options) { this.mapDivId = mapDivId; this.center = center; this.options = options; } OmekaMap.prototype = { map: null, mapDivId: null, markers: [], options: {}, center: null, markerBounds: null, clusterGroup: null, addMarker: function (latLng, options, bindHtml) { var map = this.map; var marker = L.marker(latLng, options); if (this.clusterGroup) { this.clusterGroup.addLayer(marker); } else { marker.addTo(map); } if (bindHtml) { marker.bindPopup(bindHtml, {autoPanPadding: [50, 50]}); // Fit images on the map on first load marker.once('popupopen', function (event) { var popup = event.popup; var imgs = popup.getElement().getElementsByTagName('img'); for (var i = 0; i < imgs.length; i++) { imgs[i].addEventListener('load', function imgLoadListener(event) { event.target.removeEventListener('load', imgLoadListener); // Marker autopan is disabled during panning, so defer if (map._panAnim && map._panAnim._inProgress) { map.once('moveend', function () { popup.update(); }); } else { popup.update(); } }); } }); } this.markers.push(marker); this.markerBounds.extend(latLng); return marker; }, fitMarkers: function () { if (this.markers.length == 1) { this.map.panTo(this.markers[0].getLatLng()); } else if (this.markers.length > 0) { this.map.fitBounds(this.markerBounds, {padding: [25, 25]}); } }, initMap: function () { if (!this.center) { alert('Error: The center of the map has not been set!'); return; } /* Basemap: self-hosted Protomaps vector tiles, not CARTO. * * The theme asked leaflet-providers for CartoDB.Voyager, which fetches * raster tiles from basemaps.cartocdn.com on every map view. CARTO's * basemaps are "available exclusively with an Enterprise license", so * the archive was leaning on a service it is probably not licensed for, * and one whose endpoint has already moved once. An archive should not * depend on anyone staying in business. * * basemap/protomaps-basemap-z0-5.pmtiles is a single self-hosted file * of OpenStreetMap-derived vector tiles (ODbL) served straight out of * static storage over HTTP range requests -- no API key, no account, no * vendor. See basemap/PROVENANCE.txt for how it was built. * * It carries z0-5, which is every zoom the archive opens at. Above that * the renderer over-zooms the z5 geometry: lines stay sharp, but no new * detail appears, so BASEMAP_MAX_ZOOM caps the controls rather than * letting people zoom into an empty rectangle. The three items that * were authored at z15-17 clamp to it. */ var BASEMAP_URL = '/basemap/protomaps-basemap-z0-5.pmtiles'; var BASEMAP_DATA_ZOOM = 5; var BASEMAP_MAX_ZOOM = 9; this.map = L.map(this.mapDivId, {maxZoom: BASEMAP_MAX_ZOOM}) .setView([this.center.latitude, this.center.longitude], Math.min(this.center.zoomLevel, BASEMAP_MAX_ZOOM)); this.markerBounds = L.latLngBounds(); if (typeof protomapsL === 'undefined') { // The renderer is only loaded on pages that build a map; if it is // missing, say so rather than leaving an unexplained grey box. jQuery(this.map.getContainer()).append( '
'); } else { protomapsL.leafletLayer({ url: BASEMAP_URL, // Required. Without it the layer builds tiles and paints // NOTHING -- it renders vector geometry, so it needs paint // rules before it has anything to put on the canvas, and it // fails silently: tiles load, canvases stay transparent, no // console error. The option is `flavor`, not `theme`; passing // `theme` is accepted and ignored. "light" is the pale look // closest to the CartoDB.Voyager basemap this replaces. flavor: 'light', maxDataZoom: BASEMAP_DATA_ZOOM, attribution: '© ' + 'OpenStreetMap contributors' }).addTo(this.map); } if (this.options.cluster) { this.clusterGroup = L.markerClusterGroup({ showCoverageOnHover: false }); this.map.addLayer(this.clusterGroup); } jQuery(this.map.getContainer()).trigger('o:geolocation:init_map', this); // Show the center marker if we have that enabled. if (this.center.show) { this.addMarker([this.center.latitude, this.center.longitude], {title: "(" + this.center.latitude + ',' + this.center.longitude + ")"}, this.center.markerHtml); } } }; function OmekaMapBrowse(mapDivId, center, options) { var omekaMap = new OmekaMap(mapDivId, center, options); jQuery.extend(true, this, omekaMap); this.initMap(); //XML loads asynchronously, so need to call for further config only after it has executed this.loadKmlIntoMap(this.options.uri, this.options.params); } OmekaMapBrowse.prototype = { afterLoadItems: function () { if (this.options.fitMarkers) { this.fitMarkers(); } if (!this.options.list) { return; } var listDiv = jQuery('#' + this.options.list); if (!listDiv.length) { alert('Error: You have no map links div!'); } else { //Create HTML links for each of the markers this.buildListLinks(listDiv); } }, /* Need to parse KML manually b/c Google Maps API cannot access the KML behind the admin interface */ loadKmlIntoMap: function (kmlUrl, params) { var that = this; jQuery.ajax({ type: 'GET', dataType: 'xml', url: kmlUrl, data: params, success: function(data) { var xml = jQuery(data); /* KML can be parsed as: kml - root element Placemark namewithlink description Point - longitude,latitude */ var placeMarks = xml.find('Placemark'); // If we have some placemarks, load them if (placeMarks.length) { // Retrieve the balloon styling from the KML file that.browseBalloon = that.getBalloonStyling(xml); // Build the markers from the placemarks jQuery.each(placeMarks, function (index, placeMark) { placeMark = jQuery(placeMark); that.buildMarkerFromPlacemark(placeMark); }); // We have successfully loaded some map points, so continue setting up the map object return that.afterLoadItems(); } else { // @todo Elaborate with an error message return false; } } }); }, getBalloonStyling: function (xml) { return xml.find('BalloonStyle text').text(); }, // Build a marker given the KML XML Placemark data // I wish we could use the KML file directly, but it's behind the admin interface so no go buildMarkerFromPlacemark: function (placeMark) { // Get the info for each location on the map var title = placeMark.find('name').text(); var titleWithLink = placeMark.find('namewithlink').text(); var body = placeMark.find('description').text(); var snippet = placeMark.find('Snippet').text(); // Extract the lat/long from the KML-formatted data var coordinates = placeMark.find('Point coordinates').text().split(','); var longitude = coordinates[0]; var latitude = coordinates[1]; // Use the KML formatting (do some string sub magic) var balloon = this.browseBalloon; balloon = balloon.replace('$[namewithlink]', titleWithLink).replace('$[description]', body).replace('$[Snippet]', snippet); // Build a marker, add HTML for it this.addMarker([latitude, longitude], {title: title}, balloon); }, buildListLinks: function (container) { var that = this; var list = jQuery('