* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
namespace Bobosch\OdsOsm\Provider;
use Bobosch\OdsOsm\Div;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Resource\FileRepository;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Core\Utility\PathUtility;
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
class Openlayers extends BaseProvider
{
protected $layers = [
0 => [], // Base
1 => [], // Overlay
2 => [], // Marker
];
public function getMapCore($backpath = ''): void
{
$path = ($backpath ? $backpath :
PathUtility::getAbsoluteWebPath(
GeneralUtility::getFileAbsFileName(Div::RESOURCE_BASE_PATH . 'OpenLayers/')
)
);
$pathOl = ($this->config['local_js'] ? $path : 'https://cdn.jsdelivr.net/npm/ol@v8.1.0/');
$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
$pageRenderer->addCssFile($pathOl . 'ol.css');
$this->scripts['OpenLayers'] = [
'src' => $pathOl . 'dist/ol.js',
'sri' => 'sha512-7BxMviUlJVAJOF4l717SzPknm3Y5nLAm3PPtRdrWlCu4GLaW+RhBxYuOJ1MkVNAcPu+lRWn4gtWx0PAxvTzD0g=='
];
// Do we need the layerswitcher? If so, some extra plugin is required.
if ($this->config['show_layerswitcher']) {
$pathContrib = ($this->config['local_js'] ? $path . 'Contrib/ol-layerswitcher/' : 'https://unpkg.com/ol-layerswitcher@4.1.1/dist/');
$pathCustom = $path . 'Custom/';
$pageRenderer->addCssFile($pathContrib . 'ol-layerswitcher.css');
$pageRenderer->addCssFile($pathCustom . 'ol-layerswitcher.css');
$this->scripts['OpenLayersSwitch'] = [
'src' => $pathContrib . 'ol-layerswitcher.js',
'sri' => 'sha512-HhCrrWOoQb5HSpRe1fsk9ugZQEOokbJsLioPuUhfXlr5ccRTZVg3UpnfRsTJzrdKLejmx7uvY62n2fp5qLdYQg=='
];
}
}
public function getMapMain()
{
$controls = [
'new ol.control.Attribution()',
'new ol.control.Zoom()',
'new ol.control.Rotate()'
];
if ($this->config['mouse_position']) {
$controls[] = "new ol.control.MousePosition({
coordinateFormat: ol.coordinate.createStringXY(2),
projection: 'EPSG:4326',
className: 'ods-osm-mouse-position',
target: document.getElementById('mouse-position-" . $this->config['id'] . "')
})";
}
if ($this->config['show_scalebar']) {
$controls[] = "new ol.control.ScaleLine()";
}
return "
view = new ol.View({
center: [0, 0],
zoom: 1
});
baselayergroup = new ol.layer.Group({
name: 'baselayergroup',
title: '" . LocalizationUtility::translate('base_layer', 'OdsOsm') . "',
layers: [
new ol.layer.Tile({
type: 'base',
source: new ol.source.OSM(),
})
]
});
overlaygroup = new ol.layer.Group({
name: 'overlaygroup',
title: '" . LocalizationUtility::translate('overlays', 'OdsOsm') . "',
layers: []
});
const styleCache = {};
clusters = new ol.layer.Vector({
name: 'clusters',
title: '" . LocalizationUtility::translate('openlayers.clusterLayer', 'OdsOsm') . "',
source: new ol.source.Cluster({
distance: " . (int)$this->config['cluster_radius'] . ",
minDistance: 10,
source: new ol.source.Vector({
name: 'source',
features: [],
})
}),
style: function (feature) {
const size = feature.get('features').length;
if (size > 1) {
style = new ol.style.Style({
image: new ol.style.Circle({
radius: 20,
stroke: new ol.style.Stroke({
color: '#fff',
}),
fill: new ol.style.Fill({
color: '#3399CC',
}),
}),
text: new ol.style.Text({
text: size.toString(),
fill: new ol.style.Fill({
color: '#fff',
}),
}),
});
} else {
style = feature.get('features')[0].values_.style;
}
return style;
},
});
layers = [
baselayergroup,
overlaygroup
];
var " . $this->config['id'] . " = new ol.Map({
target: '" . $this->config['id'] . "',
controls:[" . implode(',', $controls) . "],
layers: layers,
view: view
});
// Popup showing the position the user clicked
var container = document.getElementById('popup');
var closer = document.getElementById('popup-closer');
var content = document.getElementById('popup-content');
var popup = new ol.Overlay({
element: container,
autoPan: true,
autoPanAnimation: {
duration: 100
}
});
" . $this->config['id'] . ".addOverlay(popup);
closer.onclick = function () {
popup.setPosition(undefined);
content.innerHTML = '';
closer.blur();
return false;
};
";
}
/**
* The center and zoom level of the map
*
* @param float $lat: latitude
* @param float $lon: longitude
* @param int $zoom: zoom level
*
* @return string The JavaScript to set the center and zoom level
*/
public function getMapCenter($lat, $lon, $zoom)
{
return '
view.setCenter(ol.proj.transform([' . $lon . ', ' . $lat . '], \'EPSG:4326\', \'EPSG:3857\'));
view.setZoom(' . $zoom . ');
';
}
protected function getLayer($layer, $i, $backpath = '')
{
if (empty($layer['subdomains'])) {
$layer['subdomains'] = 'abc';
}
$layer['subdomains'] = substr($layer['subdomains'], 0, 1) . '-' . substr($layer['subdomains'], -1, 1);
$layer['tile_url'] = strtr($this->getTileUrl($layer), ['{s}' => '{' . $layer['subdomains'] . '}']);
if ($layer['overlay'] == 1) {
return $this->config['id'] . "_" . $i . "_overlayLayer =
new ol.layer.Tile({
visible: " . ($layer['visible'] == true ? 'true' : 'false') . ",
opacity: 0.99,
title: '" . $layer['title'] . "',
source: new ol.source.OSM({
url: '" . $layer['tile_url'] . "',
attributions: [
'" . $layer['attribution']. "'
]
})
});
overlaygroup.getLayers().push(" . $this->config['id'] . "_" . $i . "_overlayLayer);
";
}
return $this->config['id'] . "_" . $i . "_baselayergroup =
new ol.layer.Tile({
type: 'base',
combine: 'true',
visible: " . ($i == 0 ? 'true' : 'false') . ",
title: '" . $layer['title'] . "',
source: new ol.source.OSM({
url: '" . $layer['tile_url'] . "',
attributions: [
'" . $layer['attribution']. "'
]
})
});
baselayergroup.getLayers().push(" . $this->config['id'] . "_" . $i . "_baselayergroup);
";
}
/**
* Get the layer switcher
*
* @return string The JavaScript to add the layerswitcher
*/
protected function getLayerSwitcher()
{
return '
var layerSwitcher = new ol.control.LayerSwitcher({
activationMode: \'' . ($this->config['layerswitcher_activationMode'] == '1' ? 'click' : 'mouseover') . '\',
startActive: ' . ($this->config['show_layerswitcher'] == '2' ? 'true' : 'false') . ',
tipLabel: \'' . LocalizationUtility::translate('openlayers.showLayerList', 'OdsOsm') . '\',
collapseTipLabel: \'' . LocalizationUtility::translate('openlayers.hideLayerList', 'OdsOsm') . '\',
groupSelectStyle: \'children\',
reverse: false
});
' . $this->config['id'] . '.addControl(layerSwitcher);
';
}
/**
* Get the fullscreen button
*
* @return string The JavaScript to add the fullscreen button
*/
protected function getFullScreen()
{
return '
var fullScreen = new ol.control.FullScreen();
' . $this->config['id'] . '.addControl(fullScreen);
';
}
protected function getMarkers($markers)
{
$jsMarker = parent::getMarkers($markers);
// open popup? If yes, with click or hover?
switch ($this->config['show_popups']) {
case 1:
$eventMethod = 'singleclick';
break;
case 2:
$eventMethod = 'pointermove';
break;
default:
$eventMethod = false;
}
if ($eventMethod !== false) {
$jsMarker .= "
" . $this->config['id'] . ".on('" . $eventMethod . "', function (event) {
var feature = " . $this->config['id'] . ".forEachFeatureAtPixel(event.pixel, function (feat, layer) {
return feat;
});
var layer = " . $this->config['id'] . ".forEachFeatureAtPixel(event.pixel, function (feat, layer) {
return layer;
});
if (feature === undefined) {
return;
}
if (feature.get('features') === undefined) {
var coordinate = event.coordinate;
if (feature.get('desc') !== undefined) {
content.innerHTML = feature.get('desc');
} else {
// this might be some geoJSON data with properties set
var osm_popup = layer.get('popup');
var props = feature.values_,
ll = Object.keys(props),
attribute, value = '';
var osm_filter = layer.get('properties').split(',').map(item=>item.trim());
osm_filter.forEach((osm_prop) => {
if (typeof feature.get(osm_prop) !== 'undefined') {
value += '
' + osm_prop + ' ' + feature.get(osm_prop) + '';
}
});
content.innerHTML = osm_popup + '' + value + '
';
}
popup.setPosition(coordinate);
} else if (feature.get('features').length === 1) {
var singleFeature = feature.get('features')[0];
if (feature && singleFeature.get('type') == 'Point') {
var coordinate = event.coordinate;
content.innerHTML = singleFeature.get('desc');
popup.setPosition(coordinate);
}
} else if (feature && feature.get('type') == 'Point') {
var coordinate = event.coordinate;
content.innerHTML = feature.get('desc');
popup.setPosition(coordinate);
} else {
if (feature.get('features').length > 0) {
const clusterMembers = feature.get('features');
if (clusterMembers.length > 1) {
// Calculate the extent of the cluster members.
const extent = new ol.extent.createEmpty();
clusterMembers.forEach((feature) =>
ol.extent.extend(extent, feature.getGeometry().getExtent())
);
const view = " . $this->config['id'] . ".getView();
const resolution = " . $this->config['id'] . ".getView().getResolution();
if (
view.getZoom() === view.getMaxZoom() ||
(ol.extent.getWidth(extent) < resolution && ol.extent.getHeight(extent) < resolution)
) {
// Show an expanded view of the cluster members.
clickFeature = features[0];
clickResolution = resolution;
clusterCircles.setStyle(clusterCircleStyle);
} else {
// Zoom to the extent of the cluster members.
view.fit(extent, {duration: 600, padding: [100, 100, 100, 100]});
}
}
}
popup.setPosition(undefined);
}
});
";
}
// grouped marker layer
foreach ($this->layers[2] as $group_uid => $group) {
$jsMarker .= $group['layer'];
$jsMarkerFeatureBatch = [];
foreach ($group['jsMarkerFeatures'] as $id => $jsMarkerFeature) {
$jsMarker .= 'var ' . $group_uid . $id . ' = ' . $jsMarkerFeature;
$jsMarkerFeatureBatch[] = $group_uid . $id;
}
if ($this->config['cluster']) {
$jsMarker .= 'clusters.getSource().getSource().addFeatures([' . implode(',', $jsMarkerFeatureBatch) . ']);' . "\n";
} else {
$jsMarker .= $group_uid . '.getSource().addFeatures([' . implode(',', $jsMarkerFeatureBatch) . ']);' . "\n";
$jsMarker .= 'overlaygroup.getLayers().push(' . $group_uid . ');' . "\n";
}
}
if ($this->config['cluster']) {
// add cluster layer in overlaygroup
$jsMarker .= 'overlaygroup.getLayers().push(clusters);' . "\n";
}
return $jsMarker;
}
protected function getMarker($item, $table)
{
$jsMarker = '';
$jsElementVar = $table . '_' . $item['uid'];
$fileRepository = GeneralUtility::makeInstance(FileRepository::class);
// Convert item color hex value to rgba() as Openlayers doesn't have an opacity option.
if (empty($item['color'])) {
// set default blue, if nothing is given
$item['color'] = '#0009ff';
}
if (strlen($item['color']) == 7) {
$hex = [ $item['color'][1] . $item['color'][2], $item['color'][3] . $item['color'][4], $item['color'][5] . $item['color'][6] ];
$rgb = array_map('hexdec', $hex);
$opacity = '0.2';
$item['rgba'] = 'rgba('.implode(",", $rgb).','.$opacity.')';
}
switch ($table) {
case 'tx_odsosm_track':
$fileObjects = $fileRepository->findByRelation('tx_odsosm_track', 'file', $item['uid']);
if ($fileObjects) {
$file = $fileObjects[0];
} else {
break;
}
// Add tracks to layerswitcher
$this->layers[1][] = [
'title' => $item['title'],
'table' => $table,
'uid' => $item['uid']
];
// define style from given color and width
$jsMarker .= 'var ' . $jsElementVar . '_style = new ol.style.Style({
stroke: new ol.style.Stroke({
color: \''.$item['color'].'\',
width: '.($item['width'] ?: 1).'
}),
fill: new ol.style.Fill({
color: \''.$item['rgba'].'\'
}),
});';
switch (strtolower(pathinfo($file->getName(), PATHINFO_EXTENSION))) {
case 'kml':
$jsMarker .= 'var ' . $jsElementVar . '_gpx = new ol.layer.Vector({
title: \'' .$item['title'] . '\',
source: new ol.source.Vector({
projection: \'EPSG:3857\',
url: \'' . $file->getPublicUrl() . '\',
format: new ol.format.KML()
}),
style: ' . $jsElementVar . '_style
});' . "\n";
$jsMarker .= "overlaygroup.getLayers().push(" . $jsElementVar . "_gpx);";
break;
case 'gpx':
$jsMarker .= 'var ' . $jsElementVar . '_gpx = new ol.layer.Vector({
title: \'' .$item['title'] . '\',
source: new ol.source.Vector({
projection: \'EPSG:3857\',
url: \'' . $file->getPublicUrl() . '\',
format: new ol.format.GPX()
}),
style: ' . $jsElementVar . '_style
});' . "\n";
$jsMarker .= "overlaygroup.getLayers().push(" . $jsElementVar . "_gpx);";
break;
}
break;
case 'tx_odsosm_vector':
$fileObjects = $fileRepository->findByRelation('tx_odsosm_vector', 'file', $item['uid']);
// define style from given color and width
$jsMarker .= 'var ' . $jsElementVar . '_style = new ol.style.Style({
stroke: new ol.style.Stroke({
color: \''.$item['color'].'\',
width: '.($item['width'] ?: 1).'
}),
fill: new ol.style.Fill({
color: \''.$item['rgba'].'\'
}),
});' . "\n";
if ($fileObjects) {
$file = $fileObjects[0];
$filename = $file->getPublicUrl();
$properties = [
'popup' => $item['popup'] ?? '',
'properties' => $item['properties'],
];
$jsMarker .= 'var ' . $jsElementVar . '_file_properties = ' . json_encode($properties) . ';';
$jsMarker .= 'var ' . $jsElementVar . '_file = new ol.layer.Vector({
title: \'' .$item['title'] . ' ('. LocalizationUtility::translate('file', 'OdsOsm') .')\',
source: new ol.source.Vector({
projection: \'EPSG:3857\',
url: \'' . $filename . '\',
format: new ol.format.GeoJSON()
}),
style: ' . $jsElementVar . '_style,
properties: ' . $jsElementVar . '_file_properties,
});' . "\n";
$jsMarker .= $jsElementVar . "_file.getSource().setProperties(" . $jsElementVar . "_file_properties);";
$jsMarker .= "overlaygroup.getLayers().push(" . $jsElementVar . "_file);";
}
// add geojson from data field as well
if ($item['data']) {
$properties = [
'popup' => $item['popup'] ? $item['popup'] . '
' : '',
'properties' => $item['properties'],
];
$jsMarker .= 'const ' . $jsElementVar . '_geojsonObject = '. $item['data'] . ';';
$jsMarker .= 'var ' . $jsElementVar . '_data_properties = ' . json_encode($properties) . ';';
$jsMarker .= 'var ' . $jsElementVar . '_data = new ol.layer.Vector({
title: \'' .$item['title'] . '\',
source: new ol.source.Vector({
features: new ol.format.GeoJSON({
featureProjection:"EPSG:3857"
}).readFeatures(' . $jsElementVar . '_geojsonObject)
}),
style: ' . $jsElementVar . '_style
});';
$jsMarker .= $jsElementVar . "_data.setProperties(" . $jsElementVar . "_data_properties);";
$jsMarker .= "overlaygroup.getLayers().push(" . $jsElementVar . "_data);";
}
break;
default:
$markerOptions = [];
if ($item['tx_odsosm_marker'] ?? false) {
$marker = $item['tx_odsosm_marker'];
if ($marker['type'] == 'html') {
$markerOptions['icon'] = 'icon: new L.divIcon(' . json_encode($marker['icon']) . ')';
} else {
$icon = $marker['icon']->getPublicUrl();
$markerOptions['icon'] = 'icon: new L.Icon(' . json_encode($icon) . ')';
}
} else {
$icon = '/typo3conf/ext/ods_osm/Resources/Public/Icons/marker-icon.png';
$marker['size_x'] = 25;
$marker['size_y'] = 41;
}
$markerStyle = "const " . $jsElementVar . "_style = new ol.style.Style({
image: new ol.style.Icon({
anchor: [0.5, 46],
anchorXUnits: 'fraction',
anchorYUnits: 'pixels',
src: '" . $icon ."',
width: " . (int)$marker['size_x'] . ",
height: " . (int)$marker['size_y'] . "
}),
});";
// It's a group of markers
if ($item['group_title'] ?? false) {
if (empty($jsMarkerGroup)) {
$jsMarker .= $markerStyle;
$group_title = ($marker['type'] == 'html' ? $icon : "
") . ' ' . $item['group_title'];
$jsMarkerGroup = "
var " . $item['group_uid'] . " = new ol.layer.Vector({
title: \"" . $group_title . "\",
source: new ol.source.Vector({
features: []
}),
style: " . $jsElementVar . "_style
});";
$this->layers[2][$item['group_uid']]['layer'] = $jsMarkerGroup;
}
$popupJsCode = "
function (layer) {
var osm_popup = '" . ($item['popup'] ?? '') . "';
var feature = layer.feature,
props = feature.properties,
ll = Object.keys(props),
attribute, value = '';
for (attribute in props) {
value += '' + attribute + ': ' + props[attribute] + '
';
}
return osm_popup + value;
}
";
$jsMarkerFeature = "
new ol.Feature({
geometry: new ol.geom.Point(ol.proj.fromLonLat([" . $item['longitude'] . ", " . $item['latitude'] . "])),
type: 'Point',
desc: " . json_encode($item['popup']) . ",
style: " . $jsElementVar . "_style
});";
$this->layers[2][$item['group_uid']]['jsMarkerFeatures'][] = $jsMarkerFeature;
} else {
$jsMarker .= $markerStyle;
$jsMarker .= "var " . $jsElementVar . " = new ol.layer.Vector({
title: '
" . ($item['group_title'] ?? $item['name']) . "',
source: new ol.source.Vector({
features: [
new ol.Feature({
geometry: new ol.geom.Point(ol.proj.fromLonLat([" . $item['longitude'] . ", " . $item['latitude'] . "])),
type: 'Point',
desc: " . json_encode($item['popup']) . "
})
]
}),
style: " . $jsElementVar . "_style
});";
$jsMarker .= 'overlaygroup.getLayers().push(' . $jsElementVar . ');' . "\n";
}
break;
}
return $jsMarker;
}
}