Implement boundsMode fit.

This commit is contained in:
David Molineus
2015-01-27 00:02:17 +01:00
parent 37b10c14fe
commit e2eceab60e
20 changed files with 388 additions and 72 deletions

View File

@@ -1 +1 @@
L.Contao=L.Class.extend({includes:L.Mixin.Events,statics:{ATTRIBUTION:' | <a href="http://contao-leaflet.netzmacht.de/" title="Leaflet extension for Contao CMS">netzmacht <em>creative</em></a>'},maps:{},icons:{},initialize:function(){L.Icon.Default.imagePath="assets/leaflet/libs/leaflet/images",this.setGeoJsonListeners(L.GeoJSON)},addMap:function(t,e){return this.maps[t]=e,this.fire("map:added",{id:t,map:e}),this},getMap:function(t){return"undefined"==typeof this.maps[t]?null:this.maps[t]},addIcon:function(t,e){return this.icons[t]=e,this.fire("icon:added",{id:t,icon:e}),this},loadIcons:function(t){for(var e=0;e<t.length;e++){var o=L[t[e].type](t[e].options);this.addIcon(t[e].id,o)}},getIcon:function(t){return"undefined"==typeof this.icons[t]?null:this.icons[t]},load:function(t,e,o,n,i){var s=this.createRequestUrl(t),r=omnivore[e](s,o,n);return i&&(L.stamp(r),i.fire("dataloading",{layer:r}),r.on("ready",function(){i.calculateFeatureBounds(r),i.fire("dataload",{layer:r})}),r.on("error",function(){i.fire("dataload",{layer:r})})),r},pointToLayer:function(t,e){var o="marker",n=null;if(t.properties&&(t.properties.bounds=!0,t.properties.type&&(o=t.properties.type),t.properties.arguments&&(n=L[o].apply(L[o],t.properties.arguments),L.Util.setOptions(n,t.properties.options))),null===n&&(n=L[o](e,t.properties.options)),t.properties){if(t.properties.radius&&n.setRadius(t.properties.radius),t.properties.icon){var i=this.getIcon(t.properties.icon);i&&n.setIcon(i)}this.bindPopupFromFeature(n,t)}return this.fire("point:added",{marker:n,feature:t,latlng:e,type:o}),n},onEachFeature:function(t,e){t.properties&&(L.Util.setOptions(e,t.properties.options),this.bindPopupFromFeature(e,t),this.fire("feature:added",{feature:t,layer:e}))},bindPopupFromFeature:function(t,e){e.properties&&(e.properties.popup?t.bindPopup(e.properties.popup):e.properties.popupContent&&t.bindPopup(e.properties.popupContent))},setGeoJsonListeners:function(t){t&&t.prototype&&(t.prototype.options={pointToLayer:this.pointToLayer.bind(this),onEachFeature:this.onEachFeature.bind(this)})},createRequestUrl:function(t){t=encodeURIComponent(t);var e="leaflet",o=document.location.search.substr(1).split("&");if(""==o)return document.location.pathname+"?"+[e,t].join("=");for(var n,i=o.length;i--;)if(n=o[i].split("="),n[0]==e){n[1]=t,o[i]=n.join("=");break}return 0>i&&(o[o.length]=[e,t].join("=")),document.location.pathname+o.join("&")}}),L.Contao.Attribution={setPrefix:function(t){return-1===t.indexOf(L.Contao.ATTRIBUTION)&&(t+=L.Contao.ATTRIBUTION),this.options.prefix=t,this._update(),this}},L.Control.Attribution.addInitHook(function(){this.options.prefix+=L.Contao.ATTRIBUTION}),L.Control.Attribution.include(L.Contao.Attribution),L.contao=new L.Contao,L.Map.include({_dynamicBounds:null,calculateFeatureBounds:function(t,e){if(t){if(!this.options.adjustBounds&&!e)return;this._scanForBounds(t)}else this.eachLayer(this._scanForBounds,this);this._dynamicBounds&&this.fitBounds(this._dynamicBounds)},_scanForBounds:function(t){var e;!t.feature||t.feature.properties&&t.feature.properties.ignoreForBounds?L.MarkerClusterGroup&&t instanceof L.MarkerClusterGroup&&t.options.affectBounds?(e=t.getBounds(),e.isValid()&&(this._dynamicBounds?this._dynamicBounds.extend(e):this._dynamicBounds=L.latLngBounds(e.getSouthWest(),e.getNorthEast()))):(!t.options||t.options&&t.options.affectBounds)&&t.eachLayer&&t.eachLayer(this._scanForBounds,this):t.getBounds?(e=t.getBounds(),e.isValid()&&(this._dynamicBounds?this._dynamicBounds.extend(e):this._dynamicBounds=L.latLngBounds(e.getSouthWest(),e.getNorthEast()))):t.getLatLng&&(e=t.getLatLng(),this._dynamicBounds?this._dynamicBounds.extend(e):this._dynamicBounds=L.latLngBounds(e,e))}});
L.Contao=L.Class.extend({includes:L.Mixin.Events,statics:{ATTRIBUTION:' | <a href="http://contao-leaflet.netzmacht.de/" title="Leaflet extension for Contao CMS">netzmacht <em>creative</em></a>'},maps:{},icons:{},initialize:function(){L.Icon.Default.imagePath="assets/leaflet/libs/leaflet/images",this.setGeoJsonListeners(L.GeoJSON)},addMap:function(t,e){return this.maps[t]=e,this.fire("map:added",{id:t,map:e}),this},getMap:function(t){return"undefined"==typeof this.maps[t]?null:this.maps[t]},addIcon:function(t,e){return this.icons[t]=e,this.fire("icon:added",{id:t,icon:e}),this},loadIcons:function(t){for(var e=0;e<t.length;e++){var o=L[t[e].type](t[e].options);this.addIcon(t[e].id,o)}},getIcon:function(t){return"undefined"==typeof this.icons[t]?null:this.icons[t]},load:function(t,e,o,n,i){var s=this.createRequestUrl(t,i),r=omnivore[e](s,o,n);return i&&(L.stamp(r),i.options.dynamicLoad&&"fit"==r.options.boundsMode&&(r.options.requestHash=t,i.on("moveend",r.refreshData,r),i.on("layerremove",function(t){t.layer===r&&i.off("moveend",r.updateBounds,r)})),i.fire("dataloading",{layer:r}),r.on("ready",function(){i.calculateFeatureBounds(r),i.fire("dataload",{layer:r})}),r.on("error",function(){i.fire("dataload",{layer:r})})),r},pointToLayer:function(t,e){var o="marker",n=null;if(t.properties&&(t.properties.bounds=!0,t.properties.type&&(o=t.properties.type),t.properties.arguments&&(n=L[o].apply(L[o],t.properties.arguments),L.Util.setOptions(n,t.properties.options))),null===n&&(n=L[o](e,t.properties.options)),t.properties){if(t.properties.radius&&n.setRadius(t.properties.radius),t.properties.icon){var i=this.getIcon(t.properties.icon);i&&n.setIcon(i)}this.bindPopupFromFeature(n,t)}return this.fire("point:added",{marker:n,feature:t,latlng:e,type:o}),n},onEachFeature:function(t,e){t.properties&&(L.Util.setOptions(e,t.properties.options),this.bindPopupFromFeature(e,t),this.fire("feature:added",{feature:t,layer:e}))},bindPopupFromFeature:function(t,e){e.properties&&(e.properties.popup?t.bindPopup(e.properties.popup):e.properties.popupContent&&t.bindPopup(e.properties.popupContent))},setGeoJsonListeners:function(t){t&&t.prototype&&(t.prototype.options={pointToLayer:this.pointToLayer.bind(this),onEachFeature:this.onEachFeature.bind(this)})},createRequestUrl:function(t,e){var o,n="leaflet",i=document.location.search.substr(1).split("&");if(t=encodeURIComponent(t),""==i)t=document.location.pathname+"?"+[n,t].join("=");else{for(var s,r=i.length;r--;)if(s=i[r].split("="),s[0]==n){s[1]=t,i[r]=s.join("=");break}0>r&&(i[i.length]=[n,t].join("=")),t=document.location.pathname+i.join("&")}return e&&e.options.dynamicLoad&&(o=e.getBounds(),t+="&f=bbox&v=",t+=o.getSouth()+","+o.getWest(),t+=","+o.getNorth()+","+o.getEast()),t}}),L.Contao.Attribution={setPrefix:function(t){return-1===t.indexOf(L.Contao.ATTRIBUTION)&&(t+=L.Contao.ATTRIBUTION),this.options.prefix=t,this._update(),this}},L.Control.Attribution.addInitHook(function(){this.options.prefix+=L.Contao.ATTRIBUTION}),L.Control.Attribution.include(L.Contao.Attribution),L.contao=new L.Contao,L.GeoJSON.include({refreshData:function(t){var e=L.geoJson(),o=this;e.on("ready",function(){var t,e=o.getLayers();for(t=0;t<e.length;t++)o.removeLayer(e[t]);for(e=this.getLayers(),t=0;t<e.length;t++)this.removeLayer(e[t]),o.addLayer(e[t])}),omnivore.geojson(L.contao.createRequestUrl(this.options.requestHash,t.target),null,e)}}),L.Map.include({_dynamicBounds:null,calculateFeatureBounds:function(t,e){if(t){if(!this.options.adjustBounds&&!e)return;this._scanForBounds(t)}else this.eachLayer(this._scanForBounds,this);this._dynamicBounds&&this.fitBounds(this._dynamicBounds)},_scanForBounds:function(t){var e;!t.feature||t.feature.properties&&t.feature.properties.ignoreForBounds?L.MarkerClusterGroup&&t instanceof L.MarkerClusterGroup&&t.options.boundsMode&&"extend"==t.options.boundsMode?(e=t.getBounds(),e.isValid()&&(this._dynamicBounds?this._dynamicBounds.extend(e):this._dynamicBounds=L.latLngBounds(e.getSouthWest(),e.getNorthEast()))):(!t.options||t.options&&t.options.boundsMode&&"extend"==t.options.boundsMode)&&t.eachLayer&&t.eachLayer(this._scanForBounds,this):t.getBounds?(e=t.getBounds(),e.isValid()&&(this._dynamicBounds?this._dynamicBounds.extend(e):this._dynamicBounds=L.latLngBounds(e.getSouthWest(),e.getNorthEast()))):t.getLatLng&&(e=t.getLatLng(),this._dynamicBounds?this._dynamicBounds.extend(e):this._dynamicBounds=L.latLngBounds(e,e))}});

View File

@@ -119,13 +119,25 @@ L.Contao = L.Class.extend({
* @param map Pass a map object so that the data loading events are passed to the map.
*/
load: function (hash, type, options, customLayer, map) {
var url = this.createRequestUrl(hash),
var url = this.createRequestUrl(hash, map),
layer = omnivore[type](url, options, customLayer);
if (map) {
// Required because Control.Loading tries to get _leafet_id which is created here.
L.stamp(layer);
// Add listender for map bounds changes.
if (map.options.dynamicLoad && layer.options.boundsMode == 'fit') {
layer.options.requestHash = hash;
map.on('moveend', layer.refreshData, layer);
map.on('layerremove', function(e) {
if (e.layer === layer) {
map.off('moveend', layer.updateBounds, layer);
}
});
}
map.fire('dataloading', {layer: layer});
layer.on('ready', function () {
@@ -150,7 +162,7 @@ L.Contao = L.Class.extend({
* @returns {L.Marker}|{*}
*/
pointToLayer: function (feature, latlng) {
var type = 'marker';
var type = 'marker';
var marker = null;
if (feature.properties) {
@@ -238,18 +250,20 @@ L.Contao = L.Class.extend({
/**
* Create request url by appending the hash to the current url.
*
* @param {string} value The hash
* @param {string} value The hash.
* @param {L.Map} map The map.
*
* @returns {string}
*/
createRequestUrl: function (value) {
createRequestUrl: function (value, map) {
var bounds,
key = 'leaflet',
params = document.location.search.substr(1).split('&');
value = encodeURIComponent(value);
var key = 'leaflet';
var params = document.location.search.substr(1).split('&');
if (params == '') {
return document.location.pathname + '?' + [key, value].join('=');
value = document.location.pathname + '?' + [key, value].join('=');
} else {
var i = params.length;
var x;
@@ -267,8 +281,19 @@ L.Contao = L.Class.extend({
params[params.length] = [key, value].join('=');
}
return document.location.pathname + params.join('&');
value = document.location.pathname + params.join('&');
}
if (map) {
if (map.options.dynamicLoad) {
bounds = map.getBounds();
value += '&f=bbox&v=';
value += bounds.getSouth() + ',' + bounds.getWest();
value += ',' + bounds.getNorth() + ',' + bounds.getEast();
}
}
return value;
}
});

View File

@@ -0,0 +1,34 @@
/**
* Add update bounds method for geo json layers. It is triggered when map bounds changed and make a new request
* to get the data in the new bounds.
*/
L.GeoJSON.include({
/**
* Update bounds.
*
* @param {L.Event} e The subscribed event.
*/
refreshData: function(e) {
var dataLayer = L.geoJson(),
layer = this;
dataLayer.on('ready', function() {
var i, layers = layer.getLayers();
// Clear old data.
for (i = 0; i < layers.length; i++) {
layer.removeLayer(layers[i]);
}
// Copy data from temporary layer.
layers = this.getLayers();
for (i = 0; i < layers.length; i++) {
this.removeLayer(layers[i]);
layer.addLayer(layers[i]);
}
});
// TODO: Allow other data formats.
omnivore.geojson(L.contao.createRequestUrl(this.options.requestHash, e.target), null, dataLayer);
}
});

View File

@@ -1,6 +1,6 @@
/**
* Extend map so that it can calculate their bounds depending of the features with the property affectBounds.
* Extend map so that it can calculate their bounds depending of the features with the property boundsMode.
*/
L.Map.include({
_dynamicBounds: null,
@@ -63,7 +63,8 @@ L.Map.include({
this._dynamicBounds = L.latLngBounds(source, source);
}
}
} else if (L.MarkerClusterGroup && layer instanceof L.MarkerClusterGroup && layer.options.affectBounds) {
} else if (L.MarkerClusterGroup && layer instanceof L.MarkerClusterGroup
&& layer.options.boundsMode && layer.options.boundsMode == 'extend') {
source = layer.getBounds();
if (source.isValid()) {
@@ -73,7 +74,8 @@ L.Map.include({
this._dynamicBounds = L.latLngBounds(source.getSouthWest(), source.getNorthEast());
}
}
} else if ((!layer.options || (layer.options && layer.options.affectBounds)) && layer.eachLayer) {
} else if ((!layer.options || (layer.options
&& layer.options.boundsMode && layer.options.boundsMode == 'extend')) && layer.eachLayer) {
layer.eachLayer(this._scanForBounds, this);
}
}

View File

@@ -171,6 +171,10 @@ $GLOBALS['LEAFLET_LAYERS'] = array
'children' => false,
'icon' => 'system/modules/leaflet/assets/img/markers.png',
'markers' => true,
'boundsMode' => array(
'extend' => true,
'fit' => 'deferred'
),
'label' => function ($row, $label) {
$count = \Netzmacht\Contao\Leaflet\Model\MarkerModel::countBy('pid', $row['id']);
$label .= sprintf(
@@ -187,6 +191,9 @@ $GLOBALS['LEAFLET_LAYERS'] = array
'children' => false,
'icon' => 'system/modules/leaflet/assets/img/vectors.png',
'vectors' => true,
'boundsMode' => array(
'extend' => true,
),
'label' => function ($row, $label) {
$count = \Netzmacht\Contao\Leaflet\Model\VectorModel::countBy('pid', $row['id']);
$label .= sprintf(
@@ -307,3 +314,10 @@ $GLOBALS['LEAFLET_FEATURE_MODEL_PROPERTIES']['tl_leaflet_marker'][] = 'alias';
$GLOBALS['LEAFLET_FEATURE_MODEL_PROPERTIES']['tl_leaflet_vector'][] = 'id';
$GLOBALS['LEAFLET_FEATURE_MODEL_PROPERTIES']['tl_leaflet_vector'][] = 'title';
$GLOBALS['LEAFLET_FEATURE_MODEL_PROPERTIES']['tl_leaflet_vector'][] = 'alias';
/*
* Filters can be passed to a data request to get only specific data from a layer.
*/
$GLOBALS['LEAFLET_FILTERS']['bbox'] = 'Netzmacht\Contao\Leaflet\Filter\BboxFilter';
$GLOBALS['LEAFLET_FILTERS']['distance'] = 'Netzmacht\Contao\Leaflet\Filter\DistanceFilter';

View File

@@ -148,15 +148,15 @@ $GLOBALS['TL_DCA']['tl_leaflet_layer'] = array
),
'markers extends default' => array(
'+expert' => array('pointToLayer'),
'+active' => array('deferred', 'affectBounds')
'+config' => array('deferred', 'boundsMode')
),
'group extends default' => array(
'+title' => array('groupType'),
'+active' => array('affectBounds')
'+active' => array('boundsMode')
),
'vectors extends default' => array(
'+expert' => array('onEachFeature', 'pointToLayer'),
'+active' => array('deferred', 'affectBounds'),
'+config' => array('deferred', 'boundsMode'),
),
'reference extends default' => array(
'+title' => array('reference', 'standalone')
@@ -335,7 +335,7 @@ $GLOBALS['TL_DCA']['tl_leaflet_layer'] = array
'exclude' => true,
'inputType' => 'checkbox',
'default' => true,
'eval' => array('tl_class' => 'w50', 'submitOnChange' => false, 'isBoolean' => true),
'eval' => array('tl_class' => 'w50 m12', 'submitOnChange' => true, 'isBoolean' => true),
'sql' => "char(1) NOT NULL default '1'"
),
'groupType' => array
@@ -530,14 +530,14 @@ $GLOBALS['TL_DCA']['tl_leaflet_layer'] = array
'eval' => array('tl_class' => 'w50', 'submitOnChange' => false, 'isBoolean' => true),
'sql' => "char(1) NOT NULL default ''"
),
'affectBounds' => array
'boundsMode' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_leaflet_layer']['affectBounds'],
'exclude' => true,
'inputType' => 'checkbox',
'default' => false,
'eval' => array('tl_class' => 'w50'),
'sql' => "char(1) NOT NULL default ''"
'label' => &$GLOBALS['TL_LANG']['tl_leaflet_layer']['boundsMode'],
'exclude' => true,
'inputType' => 'select',
'options_callback' => array('Netzmacht\Contao\Leaflet\Dca\Layer', 'getBoundsModes'),
'eval' => array('tl_class' => 'w50', 'includeBlankOption' => true),
'sql' => "varchar(6) NOT NULL default ''"
),
'tileUrl' => array
(

View File

@@ -94,7 +94,7 @@ $GLOBALS['TL_DCA']['tl_leaflet_map'] = array
'metapalettes' => array(
'default' => array(
'title' => array('title', 'alias'),
'zoom' => array('center', 'zoom', 'adjustZoomExtra', 'adjustBounds'),
'zoom' => array('center', 'zoom', 'adjustZoomExtra', 'adjustBounds', 'dynamicLoad'),
'locate' => array('locate'),
'layers' => array('layers'),
'interaction' => array(
@@ -419,6 +419,15 @@ $GLOBALS['TL_DCA']['tl_leaflet_map'] = array
'eval' => array('tl_class' => 'clr w50', 'multiple' => true, 'helpwizard' => true),
'sql' => "varchar(255) NOT NULL default ''"
),
'dynamicLoad' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_leaflet_map']['dynamicLoad'],
'exclude' => true,
'inputType' => 'checkbox',
'default' => true,
'eval' => array('tl_class' => 'w50', 'submitOnChange' => false),
'sql' => "char(1) NOT NULL default ''"
),
'locate' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_leaflet_map']['locate'],

View File

@@ -176,18 +176,44 @@ $GLOBALS['TL_DCA']['tl_leaflet_marker'] = array
'exclude' => true,
'inputType' => 'text',
'save_callback' => array(
array('Netzmacht\Contao\Leaflet\Dca\Leaflet', 'validateCoordinate')
array('Netzmacht\Contao\Leaflet\Dca\Leaflet', 'validateCoordinate'),
array('Netzmacht\Contao\Leaflet\Dca\Marker', 'saveCoordinates')
),
'wizard' => array(
array('Netzmacht\Contao\Leaflet\Dca\Leaflet', 'getGeocoder')
),
'eval' => array(
'maxlength' => 255,
'tl_class' => 'long clr',
'nullIfEmpty' => true,
'maxlength' => 255,
'tl_class' => 'long clr',
'nullIfEmpty' => true,
'doNotSaveEmpty' => true,
),
'sql' => "varchar(255) NULL"
),
'latitude' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_leaflet_marker']['latitude'],
'exclude' => true,
'inputType' => 'text',
'eval' => array('mandatory' => false, 'maxlength' => 255, 'tl_class' => 'w50'),
'sql' => "float NULL"
),
'longitude' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_leaflet_marker']['longitude'],
'exclude' => true,
'inputType' => 'text',
'eval' => array('mandatory' => false, 'maxlength' => 255, 'tl_class' => 'w50'),
'sql' => "float NULL"
),
'altitude' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_leaflet_marker']['altitude'],
'exclude' => true,
'inputType' => 'text',
'eval' => array('mandatory' => false, 'maxlength' => 255, 'tl_class' => 'w50'),
'sql' => "float NULL"
),
'active' => array
(
'label' => &$GLOBALS['TL_LANG']['tl_leaflet_marker']['active'],

View File

@@ -76,8 +76,8 @@ $GLOBALS['TL_LANG']['tl_leaflet_layer']['iconCreateFunction'][0] = 'Crea
$GLOBALS['TL_LANG']['tl_leaflet_layer']['iconCreateFunction'][1] = 'Function used to create the cluster icon.';
$GLOBALS['TL_LANG']['tl_leaflet_layer']['disableDefaultStyle'][0] = 'Disable default style';
$GLOBALS['TL_LANG']['tl_leaflet_layer']['disableDefaultStyle'][1] = 'Do not load default marker cluster stylesheets.';
$GLOBALS['TL_LANG']['tl_leaflet_layer']['affectBounds'][0] = 'Affect map bounds';
$GLOBALS['TL_LANG']['tl_leaflet_layer']['affectBounds'][1] = 'If the map support it the marker will be used to affect the initial map bounds.';
$GLOBALS['TL_LANG']['tl_leaflet_layer']['boundsMode'][0] = 'Bounds relation';
$GLOBALS['TL_LANG']['tl_leaflet_layer']['boundsMode'][1] = 'Choose a mode how the layer data should affect the map bounds.';
$GLOBALS['TL_LANG']['tl_leaflet_layer']['minZoom'][0] = 'Minimum zoom';
$GLOBALS['TL_LANG']['tl_leaflet_layer']['minZoom'][1] = 'Minimum zoom number.';
$GLOBALS['TL_LANG']['tl_leaflet_layer']['maxZoom'][0] = 'Maximum zoom';

View File

@@ -302,6 +302,30 @@ class Layer
->execute($dataContainer->id);
}
/**
* Get bounds modes supported by the layer.
*
* @param \DataContainer $dataContainer The data container.
*
* @return array
*/
public function getBoundsModes($dataContainer)
{
$options = array();
if ($dataContainer->activeRecord && !empty($this->layers[$dataContainer->activeRecord->type]['boundsMode'])) {
foreach ($this->layers[$dataContainer->activeRecord->type]['boundsMode'] as $mode => $enabled) {
if ($enabled === true) {
$options[] = $mode;
} elseif ($enabled === 'deferred' && $dataContainer->activeRecord->deferred) {
$options[] = $mode;
}
}
}
return $options;
}
/**
* Generate a button.
*

View File

@@ -51,4 +51,37 @@ class Marker
return $builder->getOptions();
}
/**
* Save the coordinates.
*
* @param string $value The raw data.
* @param \DataContainer $dataContainer The data container driver.
*
* @return string
*/
public function saveCoordinates($value, $dataContainer)
{
$combined = array(
'latitude' => null,
'longitude' => null,
'altitude' => null
);
$values = trimsplit(',', $value);
$keys = array_keys($combined);
if (count($values) >= 2 && count($values) <= 3) {
for ($i = 0; $i < count($values); $i++) {
$combined[$keys[$i]] = $values[$i];
}
}
\Database::getInstance()
->prepare('UPDATE tl_leaflet_marker %s WHERE id=?')
->set($combined)
->execute($dataContainer->id);
return $value;
}
}

View File

@@ -11,8 +11,8 @@
namespace Netzmacht\Contao\Leaflet\Frontend;
use Netzmacht\Contao\Leaflet\Filter\Filter;
use Netzmacht\Contao\Leaflet\MapService;
use Netzmacht\Contao\Leaflet\Model\LayerModel;
/**
* The data controller handles ajax request for sub data.
@@ -36,7 +36,9 @@ class DataController
private $input = array(
'format' => 'geojson',
'type' => 'layer',
'id' => null
'id' => null,
'filter' => null,
'values' => null
);
/**
@@ -61,7 +63,13 @@ class DataController
public function execute()
{
try {
list($data, $error) = $this->loadData($this->input['type'], $this->input['id']);
if ($this->input['filter']) {
$filter = $this->createFilter();
} else {
$filter = null;
}
list($data, $error) = $this->loadData($this->input['type'], $this->input['id'], $filter);
$this->encodeData($this->input['format'], $data);
} catch (\Exception $e) {
if (\Config::get('debugMode') || \Config::get('displayErrors')) {
@@ -101,17 +109,18 @@ class DataController
*
* @param string $type The data type.
* @param mixed $dataId The data id.
* @param Filter $filter Optional request filter.
*
* @return array
*/
public function loadData($type, $dataId)
public function loadData($type, $dataId, Filter $filter = null)
{
$error = false;
$data = null;
switch ($type) {
case 'layer':
$data = $this->mapService->getFeatureCollection($dataId);
$data = $this->mapService->getFeatureCollection($dataId, $filter);
break;
default:
@@ -122,4 +131,24 @@ class DataController
return array($data, $error);
}
/**
* Create a filter.
*
* @return Filter
* @throws \RuntimeException If the filter is not defined.
*
* @SuppressWarnings(PHPMD.Superglobals)
*/
private function createFilter()
{
if (!isset($GLOBALS['LEAFLET_FILTERS'][$this->input['filter']])) {
throw new \RuntimeException(sprintf('Undefined filter "%s".', $this->input['filter']));
}
/** @var Filter $filter */
$filter = $GLOBALS['LEAFLET_FILTERS'][$this->input['filter']];
return $filter::fromRequest($this->input['values']);
}
}

View File

@@ -133,7 +133,7 @@ trait HybridTrait
*
* @return void
*
* @throws \HttpRequestException If a bad leaflet param hash is given.
* @throws \RuntimeException If a bad leaflet param hash is given.
* @SuppressWarnings(ExitExpression)
*/
private function handleAjaxRequest()
@@ -142,13 +142,15 @@ trait HybridTrait
// Handle ajax request.
if ($input) {
$data = explode(',', base64_decode($input));
$data = explode(',', base64_decode($input));
$data[] = $this->input->get('f');
$data[] = $this->input->get('v');
if (count($data) != 4) {
throw new \HttpRequestException('Bad request. Could not resolve query params');
if (count($data) != 6) {
throw new \RuntimeException('Bad request. Could not resolve query params');
}
$data = array_combine(array('for', 'type', 'id', 'format'), $data);
$data = array_combine(array('for', 'type', 'id', 'format', 'filter', 'values'), $data);
$data = array_filter($data);
if (empty($data['for']) || $data['for'] != $this->getIdentifier()) {

View File

@@ -11,6 +11,8 @@
namespace Netzmacht\Contao\Leaflet\Frontend;
use Netzmacht\Contao\Leaflet\Filter\Filter;
/**
* Class RequestUrl creates the request url.
*
@@ -39,6 +41,13 @@ class RequestUrl implements \JsonSerializable
*/
private $url;
/**
* Request filter.
*
* @var Filter|null
*/
private $filter;
/**
* Create the request url.
*
@@ -47,10 +56,11 @@ class RequestUrl implements \JsonSerializable
* @param int $dataId The data object id.
* @param string|null $type Object type. If empty it assumes a layer.
* @param string|null $format Data format. If empty it assumes geojson.
* @param Filter $filter Optional filter.
*
* @return RequestUrl
*/
public static function create($dataId, $type = null, $format = null)
public static function create($dataId, $type = null, $format = null, Filter $filter = null)
{
$params = array(
'for' => static::$for,
@@ -59,10 +69,16 @@ class RequestUrl implements \JsonSerializable
'format' => $format != 'geojson' ? $format : null
);
$hash = base64_encode(implode(',', $params));
$url = \Config::get('websitePath') . '/' . \Frontend::addToUrl('leaflet=' . $hash, false);
$hash = base64_encode(implode(',', $params));
$query = 'leaflet=' . $hash;
return new static($url, $hash);
if ($filter) {
$query .= '&amp;f=' . $filter->getName() . '&amp;v=' . $filter->toRequest();
}
$url = \Config::get('websitePath') . '/' . \Frontend::addToUrl($query, false);
return new static($url, $hash, $filter);
}
/**
@@ -109,6 +125,16 @@ class RequestUrl implements \JsonSerializable
return $this->url;
}
/**
* Get request filter.
*
* @return Filter|null
*/
public function getFilter()
{
return $this->filter;
}
/**
* Convert to string will always return the whole url.
*

View File

@@ -12,6 +12,7 @@
namespace Netzmacht\Contao\Leaflet;
use Netzmacht\Contao\Leaflet\Event\GetJavascriptEvent;
use Netzmacht\Contao\Leaflet\Filter\Filter;
use Netzmacht\Contao\Leaflet\Mapper\DefinitionMapper;
use Netzmacht\Contao\Leaflet\Model\LayerModel;
use Netzmacht\Contao\Leaflet\Model\MapModel;
@@ -68,12 +69,12 @@ class MapService
* Get map definition.
*
* @param MapModel|int $mapId The map database id. MapModel accepted as well.
* @param LatLngBounds $bounds Optional bounds where elements should be in.
* @param Filter $filter Optional request filter.
* @param string $elementId Optional element id. If none given the mapId or alias is used.
*
* @return Map
*/
public function getDefinition($mapId, LatLngBounds $bounds = null, $elementId = null)
public function getDefinition($mapId, Filter $filter = null, $elementId = null)
{
if ($mapId instanceof MapModel) {
$model = $mapId;
@@ -81,7 +82,7 @@ class MapService
$model = $this->getModel($mapId);
}
return $this->mapper->handle($model, $bounds, $elementId);
return $this->mapper->handle($model, $filter, $elementId);
}
/**
@@ -150,13 +151,13 @@ class MapService
* Get feature collection of a layer.
*
* @param LayerModel|int $layerId The layer database id or layer model.
* @param LatLngBounds $bounds Filter features in the bounds.
* @param Filter|null $filter Filter data.
*
* @return FeatureCollection
*
* @throws \InvalidArgumentException If a layer could not be found.
*/
public function getFeatureCollection($layerId, LatLngBounds $bounds = null)
public function getFeatureCollection($layerId, Filter $filter = null)
{
if ($layerId instanceof LayerModel) {
$model = $layerId;
@@ -168,6 +169,6 @@ class MapService
throw new \InvalidArgumentException(sprintf('Could not find layer "%s"', $layerId));
}
return $this->mapper->handleGeoJson($model, $bounds);
return $this->mapper->handleGeoJson($model, $filter);
}
}

View File

@@ -57,21 +57,29 @@ class MarkersLayerMapper extends AbstractLayerMapper implements GeoJsonMapper
$elementId = null
) {
if ($model->deferred) {
if ($model->pointToLayer || $model->affectBounds) {
if ($model->pointToLayer || $model->boundsMode) {
$layer = new GeoJson($this->getElementId($model, $elementId));
if ($model->pointToLayer) {
$layer->setPointToLayer(new Expression($model->pointToLayer));
}
if ($model->affectBounds) {
$layer->setOption('affectBounds', (bool) $model->affectBounds);
if ($model->boundsMode) {
$layer->setOption('boundsMode', $model->boundsMode);
}
return array($this->getElementId($model, $elementId), RequestUrl::create($model->id), array(), $layer);
return array(
$this->getElementId($model, $elementId),
RequestUrl::create($model->id, null, null, $filter),
array(),
$layer
);
}
return array($this->getElementId($model, $elementId), RequestUrl::create($model->id));
return array(
$this->getElementId($model, $elementId),
RequestUrl::create($model->id, null, null, $filter)
);
}
return parent::buildConstructArguments($model, $mapper, $filter, $elementId);
@@ -88,8 +96,8 @@ class MarkersLayerMapper extends AbstractLayerMapper implements GeoJsonMapper
Definition $parent = null
) {
if ($definition instanceof GeoJson) {
if ($model->affectBounds) {
$definition->setOption('affectBounds', true);
if ($model->boundsMode) {
$definition->setOption('boundsMode', $model->boundsMode);
}
$collection = $this->loadMarkerModels($model);
@@ -117,7 +125,7 @@ class MarkersLayerMapper extends AbstractLayerMapper implements GeoJsonMapper
public function handleGeoJson(\Model $model, DefinitionMapper $mapper, Filter $filter = null)
{
$feature = new FeatureCollection();
$collection = $this->loadMarkerModels($model);
$collection = $this->loadMarkerModels($model, $filter);
if ($collection) {
foreach ($collection as $item) {
@@ -136,12 +144,17 @@ class MarkersLayerMapper extends AbstractLayerMapper implements GeoJsonMapper
/**
* Load all layer markers.
*
* @param \Model $model The layer model.
* @param \Model $model The layer model.
* @param Filter $filter null The request filter.
*
* @return \Model\Collection|null
*/
protected function loadMarkerModels(\Model $model)
protected function loadMarkerModels(\Model $model, Filter $filter = null)
{
return MarkerModel::findActiveBy('pid', $model->id, array('order' => 'sorting'));
if ($model->boundsMode == 'fit') {
return MarkerModel::findByFilter($model->id, $filter);
}
return MarkerModel::findByFilter($model->id);
}
}

View File

@@ -68,18 +68,26 @@ class VectorsLayerMapper extends AbstractLayerMapper implements GeoJsonMapper
$options['onEachFeature'] = new Expression($model->onEachFeature);
}
if ($model->affectBounds) {
$options['affectBounds'] = (bool) $model->affectBounds;
if ($model->boundsMode) {
$options['boundsMode'] = $model->boundsMode;
}
if (!empty($options)) {
$layer = new GeoJson($this->getElementId($model, $elementId));
$layer->setOptions($options);
return array($this->getElementId($model, $elementId), RequestUrl::create($model->id), array(), $layer);
return array(
$this->getElementId($model, $elementId),
RequestUrl::create($model->id, null, null, $filter),
array(),
$layer
);
}
return array($this->getElementId($model, $elementId), RequestUrl::create($model->id));
return array(
$this->getElementId($model, $elementId),
RequestUrl::create($model->id, null, null, $filter)
);
}
return parent::buildConstructArguments($model, $mapper, $filter, $elementId);
@@ -98,8 +106,8 @@ class VectorsLayerMapper extends AbstractLayerMapper implements GeoJsonMapper
if ($definition instanceof GeoJson) {
$collection = $this->loadVectorModels($model);
if ($model->affectBounds) {
$definition->setOption('affectBounds', true);
if ($model->boundsMode) {
$definition->setOption('boundsMode', $model->boundsMode);
}
if ($collection) {

View File

@@ -100,6 +100,8 @@ class MapMapper extends AbstractMapper
if ($model->options) {
$map->setOptions(json_decode($model->options, true));
}
$map->setOption('dynamicLoad', (bool) $model->dynamicLoad);
}
/**

View File

@@ -205,23 +205,38 @@ class OptionsBuilder
public static function applyOptions($options, $definition, $model)
{
foreach ($options as $option => $mapping) {
$setter = 'set' . ucfirst($option);
$default = static::getDefaultOption($option, $definition);
if ($model->$mapping === '1' || $model->$mapping === '') {
if (((bool) $model->$mapping) !== $default) {
$definition->$setter($model->$mapping);
static::applyOption($option, $model->$mapping, $definition);
}
} elseif (is_numeric($default)) {
if ($model->$mapping != $default) {
$definition->$setter($model->$mapping);
static::applyOption($option, $model->$mapping, $definition);
}
} elseif ($model->$mapping !== $default) {
$definition->$setter($model->$mapping);
static::applyOption($option, $model->$mapping, $definition);
}
}
}
/**
* @param $option
* @param $value
* @param Definition $definition
*/
private static function applyOption($option, $value, $definition)
{
$setter = 'set' . ucfirst($option);
if (method_exists($definition, $setter)) {
$definition->$setter($value);
} else {
$definition->setOption($option, $value);
}
}
/**
* Get default option value.
*

View File

@@ -11,6 +11,10 @@
namespace Netzmacht\Contao\Leaflet\Model;
use Netzmacht\Contao\Leaflet\Filter\BboxFilter;
use Netzmacht\Contao\Leaflet\Filter\Filter;
use Netzmacht\LeafletPHP\Definition\Type\LatLngBounds;
/**
* Class MarkerModel for the tl_leaflet_marker table.
*
@@ -24,4 +28,53 @@ class MarkerModel extends AbstractActiveModel
* @var string
*/
protected static $strTable = 'tl_leaflet_marker';
/**
* @param $pid
* @param Filter $filter
*
* @return \Model\Collection|null
*/
public static function findByFilter($pid, Filter $filter = null)
{
if (!$filter) {
return MarkerModel::findActiveBy('pid', $pid, array('order' => 'sorting'));
}
switch ($filter->getName()) {
case 'bbox':
return static::findByBBoxFilter($pid, $filter);
default:
return null;
}
}
/**
* @param $pid
* @param BboxFilter $filter
*
* @return \Model\Collection|null
*/
public static function findByBBoxFilter($pid, BboxFilter $filter)
{
$columns = array(
'active=1',
'pid=?',
'latitude > ? AND latitude < ?',
'longitude > ? AND longitude < ?'
);
/** @var LatLngBounds $bounds */
$bounds = $filter->getValues()['bounds'];
$values = array(
$pid,
$bounds->getSouthWest()->getLatitude(),
$bounds->getNorthEast()->getLatitude(),
$bounds->getSouthWest()->getLongitude(),
$bounds->getNorthEast()->getLongitude()
);
return static::findBy($columns, $values, array('order' => 'sorting'));
}
}