diff --git a/assets/maps/contao-leaflet.js b/assets/maps/contao-leaflet.js index fd25c46..68b2814 100644 --- a/assets/maps/contao-leaflet.js +++ b/assets/maps/contao-leaflet.js @@ -1 +1 @@ -L.Contao=L.Class.extend({includes:L.Mixin.Events,statics:{ATTRIBUTION:' | netzmacht'},maps:{},icons:{},initialize:function(){L.Icon.Default.imagePath="assets/leaflet/libs/leaflet/images/",this.setGeoJsonListeners(L.GeoJSON)},addMap:function(t,o){return this.maps[t]=o,this.fire("map:added",{id:t,map:o}),this},getMap:function(t){return"undefined"==typeof this.maps[t]?null:this.maps[t]},addIcon:function(t,o){return this.icons[t]=o,this.fire("icon:added",{id:t,icon:o}),this},loadIcons:function(t){for(var o=0;onetzmacht'},maps:{},icons:{},initialize:function(){L.Icon.Default.imagePath="assets/leaflet/libs/leaflet/images/",this.setGeoJsonListeners(L.GeoJSON)},addMap:function(t,o){return this.maps[t]=o,this.fire("map:added",{id:t,map:o}),this},getMap:function(t){return"undefined"==typeof this.maps[t]?null:this.maps[t]},addIcon:function(t,o){return this.icons[t]=o,this.fire("icon:added",{id:t,icon:o}),this},loadIcons:function(t){for(var o=0;o=200&&t<300||304===t}function i(){void 0===a.status||n(a.status)?o.call(a,null,a):o.call(a,a,null)}var s=!1;if("undefined"==typeof window.XMLHttpRequest)return o(Error("Browser not supported"));if("undefined"==typeof e){var r=t.match(/^\s*https?:\/\/[^\/]*/);e=r&&r[0]!==location.protocol+"//"+location.hostname+(location.port?":"+location.port:"")}var a=new window.XMLHttpRequest;if(e&&!("withCredentials"in a)){a=new window.XDomainRequest;var p=o;o=function(){if(s)p.apply(this,arguments);else{var t=this,o=arguments;setTimeout(function(){p.apply(t,o)},0)}}}return"onload"in a?a.onload=i:a.onreadystatechange=function(){4===a.readyState&&i()},a.onerror=function(t){o.call(this,t||!0,null),o=function(){}},a.onprogress=function(){},a.ontimeout=function(t){o.call(this,t,null),o=function(){}},a.onabort=function(t){o.call(this,t,null),o=function(){}},a.open("GET",t,!0),a.send(null),s=!0,a}}); \ No newline at end of file diff --git a/assets/maps/src/Contao.js b/assets/maps/src/Contao.js index a088a9e..5912337 100644 --- a/assets/maps/src/Contao.js +++ b/assets/maps/src/Contao.js @@ -89,7 +89,14 @@ L.Contao = L.Class.extend({ */ loadIcons: function (icons) { for (var i = 0; i < icons.length; i++) { - var icon = L[icons[i].type](icons[i].options); + var icon; + + if (icons[i].type === 'extraMarkers.icon') { + icon = L.ExtraMarkers.icon(icons[i].options); + } else { + icon = L[icons[i].type](icons[i].options); + } + this.addIcon(icons[i].id, icon); } }, @@ -110,7 +117,7 @@ L.Contao = L.Class.extend({ }, /** - * Layer a url into a layer using omnivore. + * Load data from an url into a layer using omnivore. * * @param hash The leaflet url hash. * @param type The response content format. @@ -126,7 +133,7 @@ L.Contao = L.Class.extend({ // Required because Control.Loading tries to get _leafet_id which is created here. L.stamp(layer); - // Add listender for map bounds changes. + // Add listener for map bounds changes. if (map.options.dynamicLoad && layer.options.boundsMode == 'fit') { layer.options.requestHash = hash; map.on('moveend', layer.refreshData, layer); diff --git a/assets/maps/src/Mixin.Map.js b/assets/maps/src/Mixin.Map.js index e1b8268..110ff44 100644 --- a/assets/maps/src/Mixin.Map.js +++ b/assets/maps/src/Mixin.Map.js @@ -30,23 +30,31 @@ L.Map.include({ } if (this._dynamicBounds) { - options = {}; - - if (this.options.boundsPadding) { - options.padding = this.options.boundsPadding; - } else { - if (this.options.boundsPaddingTopLeft) { - options.paddingTopLeft = this.options.boundsPaddingTopLeft; - } - if (this.options.boundsPaddingBottomRight) { - options.paddingBottomRight = this.options.boundsPaddingBottomRight; - } - } - - this.fitBounds(this._dynamicBounds, options); + this.fitBounds(this._dynamicBounds, this.getBoundsOptions()); } }, + /** + * Get the bounds optons + * @returns {{}} + */ + getBoundsOptions: function () { + options = {}; + + if (this.options.boundsPadding) { + options.padding = this.options.boundsPadding; + } else { + if (this.options.boundsPaddingTopLeft) { + options.paddingTopLeft = this.options.boundsPaddingTopLeft; + } + if (this.options.boundsPaddingBottomRight) { + options.paddingBottomRight = this.options.boundsPaddingBottomRight; + } + } + + return options; + }, + /** * Scan recursively for bounds in a layer and extend _dynamicBounds if any found. * diff --git a/assets/maps/src/OverpassLayer.js b/assets/maps/src/OverpassLayer.js new file mode 100644 index 0000000..dbba683 --- /dev/null +++ b/assets/maps/src/OverpassLayer.js @@ -0,0 +1,227 @@ +/** + * Get the bounds as overpass bbox string. + * + * @returns {string} + */ +L.LatLngBounds.prototype.toOverpassBBoxString = function () { + var a = this._southWest, + b = this._northEast; + + return [a.lat, a.lng, b.lat, b.lng].join(","); +}; + +/** + * Implementation of the overpass layer. Heavily inspired by + * https://github.com/kartenkarsten/leaflet-layer-overpass. + */ +L.OverPassLayer = L.FeatureGroup.extend({ + options: { + minZoom: 0, + endpoint: '//overpass-api.de/api/', + query: '(node(BBOX)[organic];node(BBOX)[second_hand];);out qt;', + amenityIcons: {} + }, + /** + * Initialize the layer. + * + * @param options + */ + initialize: function (options) { + L.Util.setOptions(this, options); + + this.options.pointToLayer = this.pointToLayer; + this.options.onEachFeature = this.onEachFeature; + this.options.dynamicLoad = this.options.query.match(/BBOX/g) ? true : false; + + this._layer = L.geoJson(); + this._layers = {}; + + this.addLayer(this._layer); + }, + /** + * Refresh the data of the layer. + * + * TODO: Implement some caching. + */ + refreshData: function () { + if (this._map.getZoom() < this.options.minZoom) { + return; + } + + var bounds = this._map.getBounds().toOverpassBBoxString(); + var query = this.options.query.replace(/(BBOX)/g, bounds); + var url = this.options.endpoint + "interpreter?data=[out:json];" + query; + + this._map.fire('dataloading', {layer: this}); + + this.request(url, function (error, response) { + var data = JSON.parse(response.response); + var features = osmtogeojson(data); + var layer = L.geoJson(features, { + pointToLayer: this.options.pointToLayer.bind(this), + onEachFeature: this.options.onEachFeature.bind(this) + }); + + this.addLayer(layer); + this.removeLayer(this._layer); + this._layer = layer; + + if (this.options.boundsMode === 'extend' && layer.getBounds().isValid()) { + var bounds = this._map.getBounds(); + bounds = bounds.extend(layer.getBounds()); + + this._map.fitBounds(bounds, this._map.getBoundsOptions()); + } + + this._map.fire('dataload', {layer: this}); + }.bind(this)); + }, + /** + * @param map + */ + onAdd: function (map) { + if (this.options.boundsMode === 'fit' && this.options.dynamicLoad) { + map.on('moveend', this.refreshData, this); + } + + this.refreshData(); + }, + pointToLayer: function (feature, latlng) { + var type = 'marker'; + var icon = null; + var marker = L.marker(latlng, feature.properties.options); + + if (feature.properties) { + if (feature.properties.radius) { + marker.setRadius(feature.properties.radius); + } + + if (feature.properties.icon) { + icon = this._map.getIcon(feature.properties.icon); + + } else if (feature.properties.tags + && feature.properties.tags.amenity + && this.options.amenityIcons[feature.properties.tags.amenity] + ) { + icon = L.contao.getIcon(this.options.amenityIcons[feature.properties.tags.amenity]); + } + + if (icon) { + marker.setIcon(icon); + } + } + + if (this.options.overpassPopup) { + marker.bindPopup(this.options.overpassPopup(feature, marker)); + } + + this._map.fire('point:added', {marker: marker, feature: feature, latlng: latlng, type: type}); + + return marker; + }, + onEachFeature: function (feature, layer) { + if (feature.properties) { + L.Util.setOptions(layer, feature.properties.options); + + if (this.options.overpassPopup) { + layer.bindPopup(this.options.overpassPopup(feature, layer)); + } + + this._map.fire('feature:added', {feature: feature, layer: layer}); + } + }, + /** + * Make an ajax request. Clone of corslite from MapQuest. + */ + request: function (url, callback, cors) { + var sent = false; + + if (typeof window.XMLHttpRequest === 'undefined') { + return callback(Error('Browser not supported')); + } + + if (typeof cors === 'undefined') { + var m = url.match(/^\s*https?:\/\/[^\/]*/); + cors = m && (m[0] !== location.protocol + '//' + location.hostname + + (location.port ? ':' + location.port : '')); + } + + var x = new window.XMLHttpRequest(); + + function isSuccessful(status) { + return status >= 200 && status < 300 || status === 304; + } + + if (cors && !('withCredentials' in x)) { + // IE8-9 + x = new window.XDomainRequest(); + + // Ensure callback is never called synchronously, i.e., before + // x.send() returns (this has been observed in the wild). + // See https://github.com/mapbox/mapbox.js/issues/472 + var original = callback; + callback = function() { + if (sent) { + original.apply(this, arguments); + } else { + var that = this, args = arguments; + setTimeout(function() { + original.apply(that, args); + }, 0); + } + } + } + + function loaded() { + if ( + // XDomainRequest + x.status === undefined || + // modern browsers + isSuccessful(x.status)) callback.call(x, null, x); + else callback.call(x, x, null); + } + + // Both `onreadystatechange` and `onload` can fire. `onreadystatechange` + // has [been supported for longer](http://stackoverflow.com/a/9181508/229001). + if ('onload' in x) { + x.onload = loaded; + } else { + x.onreadystatechange = function readystate() { + if (x.readyState === 4) { + loaded(); + } + }; + } + + // Call the callback with the XMLHttpRequest object as an error and prevent + // it from ever being called again by reassigning it to `noop` + x.onerror = function error(evt) { + // XDomainRequest provides no evt parameter + callback.call(this, evt || true, null); + callback = function() { }; + }; + + // IE9 must have onprogress be set to a unique function. + x.onprogress = function() { }; + + x.ontimeout = function(evt) { + callback.call(this, evt, null); + callback = function() { }; + }; + + x.onabort = function(evt) { + callback.call(this, evt, null); + callback = function() { }; + }; + + // GET is the only supported HTTP Verb by XDomainRequest and is the + // only one supported here. + x.open('GET', url, true); + + // Send the request. Sending data is not supported. + x.send(null); + sent = true; + + return x; + } +}); diff --git a/module/assets/img/overpass.png b/module/assets/img/overpass.png new file mode 100644 index 0000000..2c41621 Binary files /dev/null and b/module/assets/img/overpass.png differ diff --git a/module/config/config.php b/module/config/config.php index 9a94685..f729f1b 100644 --- a/module/config/config.php +++ b/module/config/config.php @@ -98,6 +98,7 @@ $GLOBALS['LEAFLET_MAPPERS'][] = 'Netzmacht\Contao\Leaflet\Mapper\Layer\MarkersLa $GLOBALS['LEAFLET_MAPPERS'][] = 'Netzmacht\Contao\Leaflet\Mapper\Layer\GroupLayerMapper'; $GLOBALS['LEAFLET_MAPPERS'][] = 'Netzmacht\Contao\Leaflet\Mapper\Layer\VectorsLayerMapper'; $GLOBALS['LEAFLET_MAPPERS'][] = 'Netzmacht\Contao\Leaflet\Mapper\Layer\ReferenceLayerMapper'; +$GLOBALS['LEAFLET_MAPPERS'][] = 'Netzmacht\Contao\Leaflet\Mapper\Layer\OverpassLayerMapper'; $GLOBALS['LEAFLET_MAPPERS'][] = function () { return new \Netzmacht\Contao\Leaflet\Mapper\Layer\MarkerClusterLayerMapper( $GLOBALS['container'][\Netzmacht\Contao\Leaflet\DependencyInjection\LeafletServices::MAP_ASSETS] @@ -153,6 +154,7 @@ $GLOBALS['LEAFLET_MAPPERS'][] = function () { $GLOBALS['LEAFLET_MAPPERS'][] = 'Netzmacht\Contao\Leaflet\Mapper\UI\PopupMapper'; $GLOBALS['LEAFLET_MAPPERS'][] = 'Netzmacht\Contao\Leaflet\Mapper\Type\ImageIconMapper'; $GLOBALS['LEAFLET_MAPPERS'][] = 'Netzmacht\Contao\Leaflet\Mapper\Type\DivIconMapper'; +$GLOBALS['LEAFLET_MAPPERS'][] = 'Netzmacht\Contao\Leaflet\Mapper\Type\ExtraMarkersIconMapper'; $GLOBALS['LEAFLET_MAPPERS'][] = 'Netzmacht\Contao\Leaflet\Mapper\Style\FixedStyleMapper'; $GLOBALS['LEAFLET_MAPPERS'][] = function () { return new \Netzmacht\Contao\Leaflet\Mapper\UI\MarkerMapper( @@ -270,6 +272,21 @@ $GLOBALS['LEAFLET_LAYERS'] = array 'children' => false, 'icon' => 'system/modules/leaflet/assets/img/tile.png', ), + 'overpass' => array( + 'children' => false, + 'icon' => 'system/modules/leaflet/assets/img/overpass.png', + 'label' => function ($row, $label) { + if ($row['overpassQuery']) { + $label .= ' ' . \StringUtil::substr($row['overpassQuery'], 50) . ''; + } + + return $label; + }, + 'boundsMode' => array( + 'extend' => true, + 'fit' => true, + ), + ), ); /* @@ -285,7 +302,7 @@ $GLOBALS['LEAFLET_CONTROLS'] = array('zoom', 'layers', 'scale', 'attribution', * * Supported leaflet icon types. Register you type for the database driven definition here. */ -$GLOBALS['LEAFLET_ICONS'] = array('image', 'div'); +$GLOBALS['LEAFLET_ICONS'] = array('image', 'div', 'extra'); /* diff --git a/module/config/leaflet_amenities.php b/module/config/leaflet_amenities.php new file mode 100644 index 0000000..07e5210 --- /dev/null +++ b/module/config/leaflet_amenities.php @@ -0,0 +1,213 @@ + + * @copyright 2014-2016 netzmacht David Molineus + * @license LGPL 3.0 + * @filesource + * + */ + +return [ + 'administration', + 'advertising', + 'alm', + 'animal_boarding', + 'animal_breeding', + 'animal_shelter', + 'architect_office', + 'arts_centre', + 'artwork', + 'atm', + 'audiologist', + 'baby_hatch', + 'bank', + 'bar', + 'bbq', + 'bench', + 'bicycle_parking', + 'bicycle_rental', + 'bicycle_repair_station', + 'bicycle_trailer_sharing', + 'biergarten', + 'bikeshed', + 'boat_rental', + 'boat_sharing', + 'boat_storage', + 'brothel', + 'bts', + 'bureau_de_change', + 'bus_station', + 'cafe', + 'canoe_hire', + 'car_rental', + 'car_repair', + 'car_sharing', + 'car_wash', + 'casino', + 'charging_station', + 'childcare', + 'cinema', + 'citymap_post', + 'clinic', + 'clock', + 'club', + 'coast_guard', + 'coast_radar_station', + 'college', + 'community_center', + 'community_centre', + 'compressed_air', + 'concert_hall', + 'conference_centre', + 'courthouse', + 'coworking_space', + 'crematorium', + 'crucifix', + 'crypt', + 'customs', + 'dancing_school', + 'dead_pub', + 'dentist', + 'disused', + 'dive_centre', + 'doctors', + 'dog_bin', + 'dog_waste_bin', + 'dojo', + 'drinking_water', + '_driving_school', + 'education', + 'embassy', + 'emergency_phone', + 'emergency_service', + 'events_venue', + 'ev_charging', + 'exhibition_centre', + 'fast_food', + 'ferry_terminal', + 'festival_grounds', + 'financial_advice', + 'fire_hydrant', + 'fire_station', + 'first_aid', + 'fish_spa', + 'food_court', + 'fountain', + 'fuel', + 'gambling', + 'game_feeding', + 'garages', + 'grave_yard', + 'grit_bin', + 'harbourmaster', + 'hospice', + 'hospital', + 'hotel', + 'hunting_stand', + 'ice_cream', + 'internet_cafe', + 'jobcentre', + 'kindergarten', + 'kiosk', + 'kitchen', + 'Kneippbecken', + 'kneipp_water_cure', + 'language_school', + 'lavoir', + 'library', + 'lifeboat_station', + 'life_ring', + 'loading_dock', + 'love_hotel', + 'marae', + 'marketplace', + 'milk_dispenser', + 'mobile_library', + 'monastery', + 'money_transfer', + 'mortuary', + 'motorcycle_parking', + 'motorcycle_rental', + 'music_school', + 'music_venue', + 'nameplate', + 'nightclub', + 'nursery', + 'nursing_home', + 'park', + 'parking', + 'parking_entrance', + 'parking_space', + 'pharmacy', + 'photo_booth', + 'place_of_worship', + 'planetarium', + 'police', + 'post_box', + 'post_office', + 'preschool', + 'printer', + 'prison', + 'prison_camp', + 'proposed', + 'pub', + 'public_bath', + 'public_bookcase', + 'public_building', + 'public_hall', + 'ranger_station', + 'recycling', + 'refugee_housing', + 'register_office', + 'rescue_box', + 'rescue_station', + 'research_institute', + 'restaurant', + 'retirement_home', + 'sanatorium', + 'sanitary_dump_station', + 'sauna', + 'school', + 'scout_hut', + 'shelter', + 'shop', + 'shower', + 'ski_school', + 'smoking_area', + 'social_centre', + 'social_facility', + 'spa', + 'stables', + 'stripclub', + 'studio', + 'swimming_pool', + 'swingerclub', + 'table', + 'taxi', + 'telephone', + 'theatre', + 'ticket_booth', + 'ticket_validator', + 'toilets', + 'townhall', + 'trolley_bay', + 'university', + 'vacuum_cleaner', + 'vehicle_inspection', + 'vending_machine', + 'veterinary', + 'vivarium', + 'wash_center', + 'waste_basket', + 'waste_disposal', + 'waste_transfer_station', + 'water', + 'watering_place', + 'water_point', + 'weighbridge', + 'winery', + 'yacht_club', + 'youth_centre', +]; diff --git a/module/config/services.php b/module/config/services.php index 56c5585..8ed2ab0 100644 --- a/module/config/services.php +++ b/module/config/services.php @@ -242,7 +242,8 @@ $container['leaflet.dca.layer-callbacks'] = $container->share( $container[Services::DATABASE_CONNECTION], $container[Services::TRANSLATOR], $GLOBALS['LEAFLET_LAYERS'], - $GLOBALS['LEAFLET_TILE_PROVIDERS'] + $GLOBALS['LEAFLET_TILE_PROVIDERS'], + require TL_ROOT . '/system/modules/leaflet/config/leaflet_amenities.php' ); } ); diff --git a/module/dca/tl_leaflet_icon.php b/module/dca/tl_leaflet_icon.php index ad7659a..b9ef4fc 100644 --- a/module/dca/tl_leaflet_icon.php +++ b/module/dca/tl_leaflet_icon.php @@ -150,7 +150,21 @@ $GLOBALS['TL_DCA']['tl_leaflet_icon'] = array 'active' => array( 'active' ) - ) + ), + + 'extra extends default' => array( + 'config' => array( + 'icon', + 'prefix', + 'shape', + 'markerColor', + 'number', + 'iconColor', + ), + 'active' => array( + 'active' + ) + ), ), 'fields' => array @@ -351,5 +365,85 @@ $GLOBALS['TL_DCA']['tl_leaflet_icon'] = array ), 'sql' => "mediumtext NULL" ), + 'icon' => array + ( + 'label' => &$GLOBALS['TL_LANG']['tl_leaflet_icon']['icon'], + 'exclude' => true, + 'inputType' => 'text', + 'eval' => array( + 'maxlength' => 64, + 'tl_class' => 'w50', + 'nullIfEmpty' => true, + ), + 'sql' => "varchar(64) NULL" + ), + 'prefix' => array + ( + 'label' => &$GLOBALS['TL_LANG']['tl_leaflet_icon']['prefix'], + 'exclude' => true, + 'inputType' => 'text', + 'eval' => array( + 'maxlength' => 64, + 'tl_class' => 'w50', + 'nullIfEmpty' => true, + ), + 'sql' => "varchar(64) NULL" + ), + 'shape' => array + ( + 'label' => &$GLOBALS['TL_LANG']['tl_leaflet_icon']['shape'], + 'exclude' => true, + 'inputType' => 'select', + 'default' => 'circle', + 'options' => ['circle', 'square', 'star', 'penta'], + 'eval' => array( + 'tl_class' => 'w50', + ), + 'sql' => "varchar(64) NULL" + ), + 'iconColor' => array + ( + 'label' => &$GLOBALS['TL_LANG']['tl_leaflet_icon']['iconColor'], + 'exclude' => true, + 'inputType' => 'text', + 'wizard' => array( + \Netzmacht\Contao\Toolkit\Dca\Callback\CallbackFactory::colorPicker() + ), + 'eval' => array( + 'maxlength' => 64, + 'tl_class' => 'w50 wizard', + 'nullIfEmpty' => true, + ), + 'sql' => "varchar(16) NULL" + ), + 'markerColor' => array + ( + 'label' => &$GLOBALS['TL_LANG']['tl_leaflet_icon']['markerColor'], + 'exclude' => true, + 'inputType' => 'select', + 'default' => 'circle', + 'options' => [ + 'blue', + 'red', + 'orange-dark', + 'orange', + 'yellow', + 'blue-dark', + 'cyan', + 'purple', + 'violet', + 'pink', + 'green-dark', + 'green', + 'green-light', + 'black', + 'white' + ], + 'eval' => array( + 'tl_class' => 'w50', + 'nullIfEmpty' => true, + ), + 'sql' => "varchar(16) NULL" + ), ), ); diff --git a/module/dca/tl_leaflet_layer.php b/module/dca/tl_leaflet_layer.php index 9c9988c..bd36502 100644 --- a/module/dca/tl_leaflet_layer.php +++ b/module/dca/tl_leaflet_layer.php @@ -153,6 +153,7 @@ $GLOBALS['TL_DCA']['tl_leaflet_layer'] = array 'default' => array( 'title' => array('title', 'alias', 'type'), 'config' => array(), + 'style' => array(), 'expert' => array(':hide'), 'active' => array('active'), ), @@ -213,7 +214,23 @@ $GLOBALS['TL_DCA']['tl_leaflet_layer'] = array 'reuseTiles', 'bounds' ) - ) + ), + 'overpass extends default' => array( + 'config' => array( + 'overpassQuery', + 'boundsMode', + 'minZoom', + 'overpassEndpoint', + 'overpassPopup' + ), + 'style' => array( + 'amenityIcons' + ), + '+expert' => array( + 'onEachFeature', + 'pointToLayer', + ), + ), ), 'metasubselectpalettes' => array( @@ -779,9 +796,135 @@ $GLOBALS['TL_DCA']['tl_leaflet_layer'] = array 'label' => &$GLOBALS['TL_LANG']['tl_leaflet_layer']['cacheLifeTime'], 'exclude' => true, 'inputType' => 'text', - 'default' => null, - 'eval' => array('maxlength' => 5, 'rgxp' => 'digit', 'tl_class' => 'w50', 'nullIfEmpty' => true), - 'sql' => "int(9) NOT NULL default '0'" + 'default' => 0, + 'eval' => array('maxlength' => 5, 'rgxp' => 'digit', 'tl_class' => 'w50'), + 'sql' => "int(10) unsigned NOT NULL default '0'" + ), + 'overpassQuery' => array + ( + 'label' => &$GLOBALS['TL_LANG']['tl_leaflet_layer']['overpassQuery'], + 'exclude' => true, + 'inputType' => 'textarea', + 'eval' => array( + 'preserveTags' => true, + 'decodeEntities' => true, + 'allowHtml' => true, + 'rte' => 'ace', + 'tl_class' => 'clr' + ), + 'sql' => "mediumtext NULL" + ), + 'overpassEndpoint' => array + ( + 'label' => &$GLOBALS['TL_LANG']['tl_leaflet_layer']['overpassEndpoint'], + 'exclude' => true, + 'inputType' => 'text', + 'default' => '', + 'eval' => array('tl_class' => 'w50'), + 'sql' => "varchar(255) NOT NULL default ''" + ), + 'overpassCallback' => array + ( + 'label' => &$GLOBALS['TL_LANG']['tl_leaflet_layer']['overpassCallback'], + 'exclude' => true, + 'inputType' => 'textarea', + 'eval' => array( + 'preserveTags' => true, + 'decodeEntities' => true, + 'allowHtml' => true, + 'rte' => 'ace|javascript', + 'tl_class' => 'clr' + ), + 'sql' => "mediumtext NULL" + ), + 'minZoomIndicatorPosition' => array + ( + 'label' => &$GLOBALS['TL_LANG']['tl_leaflet_layer']['minZoomIndicatorPosition'], + 'exclude' => true, + 'inputType' => 'select', + 'filter' => true, + 'sorting' => true, + 'options' => array('topleft', 'topright', 'bottomleft', 'bottomright'), + 'reference' => &$GLOBALS['TL_LANG']['tl_leaflet_layer'], + 'eval' => array('mandatory' => true, 'maxlength' => 255, 'tl_class' => 'w50', 'helpwizard' => true), + 'sql' => "varchar(255) NOT NULL default ''" + ), + 'minZoomIndicatorMessage' => array + ( + 'label' => &$GLOBALS['TL_LANG']['tl_leaflet_layer']['minZoomIndicatorMessage'], + 'exclude' => true, + 'inputType' => 'text', + 'default' => '', + 'eval' => array('tl_class' => 'clr w50'), + 'sql' => "varchar(255) NOT NULL default ''" + ), + 'minZoomIndicatorMessageNoLayer' => array + ( + 'label' => &$GLOBALS['TL_LANG']['tl_leaflet_layer']['minZoomIndicatorMessageNoLayer'], + 'exclude' => true, + 'inputType' => 'text', + 'default' => '', + 'eval' => array('tl_class' => 'w50'), + 'sql' => "varchar(255) NOT NULL default ''" + ), + 'debug' => array + ( + 'label' => &$GLOBALS['TL_LANG']['tl_leaflet_layer']['debug'], + 'exclude' => true, + 'inputType' => 'checkbox', + 'default' => false, + 'eval' => array('tl_class' => 'w50 m12'), + 'sql' => "char(1) NOT NULL default ''" + ), + 'amenityIcons' => array + ( + 'label' => &$GLOBALS['TL_LANG']['tl_leaflet_layer']['amenityIcons'], + 'exclude' => true, + 'inputType' => 'multiColumnWizard', + 'options_callback' => array('Netzmacht\Contao\Leaflet\Dca\MarkerCallbacks', 'getIcons'), + 'eval' => array( + 'columnFields' => array( + 'amenity' => array( + 'label' => &$GLOBALS['TL_LANG']['tl_leaflet_layer']['amenity'], + 'exclude' => true, + 'inputType' => 'select', + 'options_callback' => \Netzmacht\Contao\Leaflet\Dca\LayerCallbacks::callback('getAmenities'), + 'eval' => array( + 'mandatory' => true, + 'tl_class' => 'w50', + 'style' => 'width: 200px', + 'chosen' => true, + ), + ), + 'icon' => array( + 'label' => &$GLOBALS['TL_LANG']['tl_leaflet_layer']['amenityIcon'], + 'exclude' => true, + 'inputType' => 'select', + 'options_callback' => array('Netzmacht\Contao\Leaflet\Dca\MarkerCallbacks', 'getIcons'), + 'eval' => array( + 'mandatory' => true, + 'tl_class' => 'w50', + 'style' => 'width: 200px', + 'chosen' => true, + ), + ), + ), + ), + 'sql' => "blob NULL", + ), + 'overpassPopup' => array + ( + 'label' => &$GLOBALS['TL_LANG']['tl_leaflet_layer']['overpassPopup'], + 'exclude' => true, + 'inputType' => 'textarea', + 'eval' => array( + 'preserveTags' => true, + 'decodeEntities' => true, + 'allowHtml' => true, + 'rte' => 'ace|javascript', + 'tl_class' => 'clr' + ), + 'sql' => "mediumtext NULL" ), ) ); diff --git a/module/languages/en/leaflet.php b/module/languages/en/leaflet.php index 9285a61..bcd3324 100644 --- a/module/languages/en/leaflet.php +++ b/module/languages/en/leaflet.php @@ -27,6 +27,8 @@ $GLOBALS['TL_LANG']['leaflet_layer']['markercluster'][0] = 'Marker cluster'; $GLOBALS['TL_LANG']['leaflet_layer']['markercluster'][1] = 'Marker cluster layer based on Leaflet.markerclusterdatabase = $database; $this->layers = $layers; $this->tileProviders = $tileProviders; - - \Controller::loadLanguageFile('leaflet_layer'); - - $this->translator = $translator; + $this->manager = $manager; + $this->translator = $translator; + $this->amenities = $amenities; } /** @@ -359,6 +373,16 @@ class LayerCallbacks extends Callbacks ->getOptions(); } + /** + * Get all know osm amenities as options. + * + * @return array + */ + public function getAmenities() + { + return $this->amenities; + } + /** * Generate a button. * diff --git a/src/Netzmacht/Contao/Leaflet/Definition/Layer/OverpassLayer.php b/src/Netzmacht/Contao/Leaflet/Definition/Layer/OverpassLayer.php new file mode 100644 index 0000000..cd94ff7 --- /dev/null +++ b/src/Netzmacht/Contao/Leaflet/Definition/Layer/OverpassLayer.php @@ -0,0 +1,190 @@ + + * @copyright 2016 netzmacht David Molineus. All rights reserved. + * @filesource + * + */ + +namespace Netzmacht\Contao\Leaflet\Definition\Layer; + +use Netzmacht\JavascriptBuilder\Encoder; +use Netzmacht\JavascriptBuilder\Type\AnonymousFunction; +use Netzmacht\JavascriptBuilder\Type\ConvertsToJavascript; +use Netzmacht\JavascriptBuilder\Type\Expression; +use Netzmacht\LeafletPHP\Definition\AbstractLayer; +use Netzmacht\LeafletPHP\Definition\HasOptions; +use Netzmacht\LeafletPHP\Encoder\EncodeHelperTrait; + +/** + * Class OverpassLayer provides implementation of https://github.com/kartenkarsten/leaflet-layer-overpass. + * + * @package Netzmacht\LeafletPHP\Plugins\OverpassLayer + */ +class OverpassLayer extends AbstractLayer implements HasOptions, ConvertsToJavascript +{ + use EncodeHelperTrait; + + /** + * {@inheritdoc} + */ + public static function getType() + { + return 'OverpassLayer'; + } + + /** + * {@inheritdoc} + */ + public static function getRequiredLibraries() + { + $libs = parent::getRequiredLibraries(); + $libs[] = 'osmtogeojson'; + + return $libs; + } + + /** + * OverpassLayer constructor. + * + * @param string $identifier Indicator of the layer. + * @param array $options Options. + */ + public function __construct($identifier, array $options = []) + { + parent::__construct($identifier); + + $this->setOptions($options); + } + + /** + * Set the debug mode. + * + * @param bool $debug Debug mode. + * + * @return $this + */ + public function setDebug($debug) + { + return $this->setOption('debug', (bool) $debug); + } + + /** + * Get debug mode. + * + * @return bool + */ + public function getDebug() + { + return $this->getOption('debug', false); + } + + /** + * Set the query. + * + * @param string $query Query. + * + * @return $this + */ + public function setQuery($query) + { + return $this->setOption('query', $query); + } + + /** + * Get query. + * + * @return bool + */ + public function getQuery() + { + return $this->getOption('query', '(node(BBOX)[organic];node(BBOX)[second_hand];);out qt;'); + } + + /** + * Set the endpoint. + * + * @param string $endpoint Endpoint. + * + * @return $this + */ + public function setEndpoint($endpoint) + { + return $this->setOption('endpoint', $endpoint); + } + + /** + * Get endpoint. + * + * @return bool + */ + public function getEndpoint() + { + return $this->getOption('endpoint', '//overpass-api.de/api/'); + } + + /** + * Set point to layer function. + * + * @param Expression|AnonymousFunction $function The function callback. + * + * @return $this + */ + public function setPointToLayer($function) + { + return $this->setOption('pointToLayer', $function); + } + + /** + * Set on each feature function. + * + * @param Expression|AnonymousFunction $function The function callback. + * + * @return $this + */ + public function setOnEachFeature($function) + { + return $this->setOption('onEachFeature', $function); + } + + /** + * Set the minZoom. + * + * @param int $minZoom MinZoom. + * + * @return $this + */ + public function setMinZoom($minZoom) + { + return $this->setOption('minZoom', (int) $minZoom); + } + + /** + * Get minZoom. + * + * @return bool + */ + public function getMinZoom() + { + return $this->getOption('minZoom', 15); + } + + /** + * {@inheritdoc} + */ + public function encode(Encoder $encoder, $flags = null) + { + $buffer = sprintf ( + '%s = new L.OverPassLayer(%s)%s', + $encoder->encodeReference($this), + $encoder->encodeArray($this->getOptions(), JSON_FORCE_OBJECT), + $encoder->close($flags) + ); + + $buffer .= $this->encodeMethodCalls($this->getMethodCalls(), $encoder, $flags); + + return $buffer; + } +} diff --git a/src/Netzmacht/Contao/Leaflet/Mapper/Layer/OverpassLayerMapper.php b/src/Netzmacht/Contao/Leaflet/Mapper/Layer/OverpassLayerMapper.php new file mode 100644 index 0000000..95ccca8 --- /dev/null +++ b/src/Netzmacht/Contao/Leaflet/Mapper/Layer/OverpassLayerMapper.php @@ -0,0 +1,124 @@ + + * @copyright 2016 netzmacht David Molineus. All rights reserved. + * @filesource + * + */ + +namespace Netzmacht\Contao\Leaflet\Mapper\Layer; + +use Model; +use Netzmacht\Contao\Leaflet\Definition\Layer\OverpassLayer; +use Netzmacht\Contao\Leaflet\Filter\Filter; +use Netzmacht\Contao\Leaflet\Mapper\DefinitionMapper; +use Netzmacht\Contao\Leaflet\Model\IconModel; +use Netzmacht\JavascriptBuilder\Type\Expression; +use Netzmacht\LeafletPHP\Definition; + +/** + * Class OverpassLayerMapper + * + * @package Netzmacht\Contao\Leaflet\Mapper\Layer + */ +class OverpassLayerMapper extends AbstractLayerMapper +{ + /** + * The definition type. + * + * @var string + */ + protected static $type = 'overpass'; + + /** + * The definition class. + * + * @var string + */ + protected static $definitionClass = 'Netzmacht\Contao\Leaflet\Definition\Layer\OverpassLayer'; + + /** + * {@inheritdoc} + */ + protected function initialize() + { + parent::initialize(); + + $this->optionsBuilder + ->addOption('query', 'overpassQuery') + ->addOption('minZoom') + ->addOption('boundsMode') + ->addOption('overpassEndpoint', 'endpoint'); + } + + /** + * {@inheritdoc} + */ + protected function build( + Definition $definition, + Model $model, + DefinitionMapper $mapper, + Filter $filter = null, + Definition $parent = null + ) { + if (!$definition instanceof OverpassLayer) { + return; + } + + $amenityIconsMap = $this->buildAmenityIconsMap($model); + $definition->setOption('amenityIcons', $amenityIconsMap); + + if ($model->pointToLayer) { + $definition->setPointToLayer(new Expression($model->pointToLayer)); + } + + if ($model->onEachFeature) { + $definition->setOnEachFeature(new Expression($model->onEachFeature)); + } + + if ($model->overpassPopup) { + $definition->setOption('overpassPopup', new Expression($model->overpassPopup)); + } + } + + /** + * Build the amenity icons map. + * + * @param Model $model Definition model. + * + * @return array + */ + protected function buildAmenityIconsMap(Model $model) + { + $amenityIconsConfig = deserialize($model->amenityIcons, true); + $amenityIconsMap = []; + foreach ($amenityIconsConfig as $config) { + if (!$config['amenity'] || !$config['icon']) { + continue; + } + + $amenityIconsMap[$config['amenity']] = $config['icon']; + } + + if ($amenityIconsMap) { + $collection = IconModel::findMultipleByIds(array_unique($amenityIconsMap)); + $icons = []; + + if ($collection) { + foreach ($collection as $iconModel) { + $icons[$iconModel->id] = $iconModel->alias ?: $iconModel->id; + } + + foreach ($amenityIconsMap as $amenity => $iconId) { + if (isset($icons[$iconId])) { + $amenityIconsMap[$amenity] = $icons[$iconId]; + } + } + } + } + + return $amenityIconsMap; + } +} diff --git a/src/Netzmacht/Contao/Leaflet/Mapper/OptionsBuilder.php b/src/Netzmacht/Contao/Leaflet/Mapper/OptionsBuilder.php index e1e9d17..377e7f0 100644 --- a/src/Netzmacht/Contao/Leaflet/Mapper/OptionsBuilder.php +++ b/src/Netzmacht/Contao/Leaflet/Mapper/OptionsBuilder.php @@ -162,7 +162,7 @@ class OptionsBuilder * * @return void */ - private function buildConditionals(Definition $definition, \Model $model) + private function buildConditionals($definition, \Model $model) { foreach ($this->conditional as $column => $conditions) { foreach ($conditions as $value => $options) { diff --git a/src/Netzmacht/Contao/Leaflet/Mapper/Type/ExtraMarkersIconMapper.php b/src/Netzmacht/Contao/Leaflet/Mapper/Type/ExtraMarkersIconMapper.php new file mode 100644 index 0000000..a78aed7 --- /dev/null +++ b/src/Netzmacht/Contao/Leaflet/Mapper/Type/ExtraMarkersIconMapper.php @@ -0,0 +1,66 @@ + + * @copyright 2014-2016 netzmacht David Molineus + * @license LGPL 3.0 + * @filesource + * + */ + +namespace Netzmacht\Contao\Leaflet\Mapper\Type; + +use Netzmacht\Contao\Leaflet\Filter\Filter; +use Netzmacht\Contao\Leaflet\Mapper\DefinitionMapper; +use Netzmacht\LeafletPHP\Definition; +use Netzmacht\LeafletPHP\Definition\Type\DivIcon; + +/** + * Class DivIconMapper maps the icon model to the div icon definition. + * + * @package Netzmacht\Contao\Leaflet\Mapper\Type + */ +class ExtraMarkersIconMapper extends AbstractIconMapper +{ + /** + * Class of the definition being created. + * + * @var string + */ + protected static $definitionClass = 'Netzmacht\LeafletPHP\Plugins\ExtraMarkers\ExtraMarkersIcon'; + + /** + * Layer type. + * + * @var string + */ + protected static $type = 'extra'; + + /** + * {@inheritdoc} + */ + protected function initialize() + { + parent::initialize(); + + $this->optionsBuilder->addOptions(['icon', 'iconColor', 'markerColor', 'shape', 'number', 'prefix']); + } + + /** + * {@inheritdoc} + */ + protected function build( + Definition $definition, + \Model $model, + DefinitionMapper $mapper, + Filter $filter = null, + Definition $parent = null + ) { + parent::build($definition, $model, $mapper, $filter); + + if ($definition instanceof DivIcon && $model->iconSize) { + $definition->setIconSize(explode(',', $model->iconSize, 2)); + } + } +} diff --git a/src/Netzmacht/Contao/Leaflet/Subscriber/BootSubscriber.php b/src/Netzmacht/Contao/Leaflet/Subscriber/BootSubscriber.php index 2f4aaed..57b5cb7 100644 --- a/src/Netzmacht/Contao/Leaflet/Subscriber/BootSubscriber.php +++ b/src/Netzmacht/Contao/Leaflet/Subscriber/BootSubscriber.php @@ -24,7 +24,6 @@ use Netzmacht\Contao\Leaflet\Mapper\Mapper; use Netzmacht\Contao\Leaflet\Model\IconModel; use Netzmacht\Contao\Toolkit\Boot\Event\InitializeSystemEvent; use Netzmacht\Contao\Toolkit\DependencyInjection\Services; -use Netzmacht\Contao\Toolkit\View\Assets\AssetsManager; use Netzmacht\LeafletPHP\Assets; use Netzmacht\LeafletPHP\Definition\Type\ImageIcon; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -101,7 +100,7 @@ class BootSubscriber implements EventSubscriberInterface InitializeDefinitionMapperEvent::NAME => 'initializeDefinitionMapper', InitializeEventDispatcherEvent::NAME => 'initializeEventDispatcher', InitializeLeafletBuilderEvent::NAME => 'initializeLeafletBuilder', - GetJavascriptEvent::NAME => array(array('loadAssets'), array('loadIcons')), + GetJavascriptEvent::NAME => array(array('loadIcons'), array('loadAssets')), ); } @@ -190,6 +189,7 @@ class BootSubscriber implements EventSubscriberInterface public function loadAssets() { $this->assets->addJavascript('assets/leaflet/maps/contao-leaflet.js', ContaoAssets::TYPE_FILE); + $this->assets->addJavascript('assets/leaflet/js/icons.js', ContaoAssets::TYPE_FILE); } /** @@ -217,20 +217,37 @@ class BootSubscriber implements EventSubscriberInterface 'type' => lcfirst($icon->getType()), 'options' => $icon->getOptions(), ); + + foreach ($icon::getRequiredLibraries() as $library) { + if (!isset($this->libraries[$library])) { + continue; + } + + $assets = $this->libraries[$library]; + + if (!empty($assets['css'])) { + list ($source, $type) = (array) $assets['css']; + $this->assets->addStylesheet($source, $type ?: Assets::TYPE_FILE); + } + + if (!empty($assets['javascript'])) { + list ($source, $type) = (array) $assets['javascript']; + $this->assets->addJavascript($source, $type ?: Assets::TYPE_FILE); + } + } } if ($icons) { $buffer = sprintf('L.contao.loadIcons(%s);', json_encode($icons)); } - $file = new \File('assets/leaflet/js/icons.js'); - $file->write($buffer); - $file->close(); - // @codingStandardsIgnoreStart // TODO: Cache it. // codingStandardsIgnoreEnd - $this->assets->addJavascript('assets/leaflet/js/icons.js', ContaoAssets::TYPE_FILE); + + $file = new \File('assets/leaflet/js/icons.js'); + $file->write($buffer); + $file->close(); } }