Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add MapLibre provider #9

Draft
wants to merge 17 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ After installation, it is possible to change some settings in order to let this

### provider

Specify the provider you want to use for maps.
Currently, only Google Maps is supported for interactive maps.
Specify the provider you want to use for interactive maps.

* `GoogleMaps`
* `OpenStreetMaps`
* `MapQuest`
* `MapLibre`
* `MapTiler`
* `OpenStreetMap`

### api_key

Expand All @@ -47,13 +47,24 @@ Example values for `staticmapurl`:

* [Google Maps](https://developers.google.com/maps/documentation/static-maps/intro):
`https://maps.googleapis.com/maps/api/staticmap?markers=%f,%f&size=%dx%d&key=%s`.
* [Open Street Map](https://wiki.openstreetmap.org/wiki/StaticMapLite):
`http://staticmap.openstreetmap.de/staticmap.php?center=%1$f,%2$f&markers=%1$f,%2$f,red-pushpin&size=%3$dx%4$d&zoom=%6$d`
* [MapQuest](https://developer.mapquest.com/documentation/static-map-api/v5/):
`https://www.mapquestapi.com/staticmap/v5/map?locations=%f,%f&size=%d,%d&key=%s&zoom=%d`
* [MapTiler](https://docs.maptiler.com/cloud/api/static-maps/):
`https://api.maptiler.com/maps/bright-v2/static/auto/%3$dx%4$d@2x.png?markers=%1$f,%2$f&key=%5$s`

Note that you can also use [QR Code Generator](http://goqr.me/api/doc/create-qr-code/) as thumbnail generator.
The resulting value for `staticmapurl` then looks like: `https://api.qrserver.com/v1/create-qr-code/?data=geo:%f,%f&size=%dx%d&bgcolor=eee`
The resulting value for `staticmapurl` then looks like: `https://api.qrserver.com/v1/create-qr-code/?data=geo:%f,%f&size=%dx%d`

### style

When using a MapLibre provider, you can use a custom [style specification](https://maplibre.org/maplibre-gl-js/docs/style-spec/).
The value is either a URL to the style JSON file or a representation of the style.

The module has default values for the following providers:

* [MapTiler](https://docs.maptiler.com/cloud/api/maps/#style-json-of-the-map):
`https://api.maptiler.com/maps/bright-v2/style.json?key=YOUR_MAPTILER_API_KEY`
* OpenStreetMap

### default_latitude

Expand Down Expand Up @@ -118,7 +129,7 @@ Display rank.
Height of the interactive map.
Defaults to 600.
* search _(optional)_
Whether or not to activate address search.
Whether to activate address search.
Defaults to "false".
* query _(mandatory)_
The OQL query to select the objects to be placed on the map.
Expand Down
189 changes: 108 additions & 81 deletions geomap.class.inc.php
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
<?php
/**
* @copyright Copyright (C) 2019-2022 Super-Visions
* @copyright 2019-2025 Super-Visions
* @license http://opensource.org/licenses/AGPL-3.0
*/

use Combodo\iTop\Application\UI\Base\Component\Html\Html;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;

class GeoMap extends Dashlet
{
static protected $aAttributeList;

/**
* @var array{string, array{string, string}}
*/
static protected array $aAttributeList;

/**
* @param ModelReflection $oModelReflection
* @param string $sId
Expand All @@ -23,27 +27,20 @@ public function __construct(ModelReflection $oModelReflection, $sId)
$this->aProperties['query'] = 'SELECT Location';
$this->aProperties['attribute'] = '';
}

/**
* @inheritDoc
* @throws Exception
*/
public function Render($oPage, $bEditMode = false, $aExtraParams = array())
public function Render($oPage, $bEditMode = false, $aExtraParams = array()): UIContentBlock
{
// Load values
$sApiKey = utils::GetConfig()->GetModuleSetting('sv-geolocation', 'api_key');
$iDefaultLat = utils::GetConfig()->GetModuleSetting('sv-geolocation', 'default_latitude');
$iDefaultLng = utils::GetConfig()->GetModuleSetting('sv-geolocation', 'default_longitude');
$iZoom = utils::GetConfig()->GetModuleSetting('sv-geolocation', 'default_zoom');
$sId = sprintf('map_%d%s', $this->sId, $bEditMode ? '_edit' : '' );

$oFilter = DBObjectSearch::FromOQL($this->aProperties['query']);
$sCreateUrl = null;
if (UserRights::IsActionAllowed($oFilter->GetClass(), UR_ACTION_MODIFY))
{
$sCreateUrl = sprintf(utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=new&class=%s&default[%s]=', $oFilter->GetClass(), $this->aProperties['attribute']);
}
$oBlock = null;

$sId = sprintf('map_%d%s', $this->sId, $bEditMode ? '_edit' : '');

// Prepare page
$oPage->add_dict_entry('UI:ClickToCreateNew');
$oPage->add_style(<<<STYLE
Expand All @@ -59,83 +56,103 @@ public function Render($oPage, $bEditMode = false, $aExtraParams = array())
}
STYLE
);

$sDisplaySearch = $this->aProperties['search'] ? 'block' : 'none';
$sSearch = Dict::S('UI:Button:Search');
$sBackgroundUrl = utils::GetAbsoluteUrlModulesRoot().'sv-geolocation/images/world-map.jpg';
$sBackgroundUrl = utils::GetAbsoluteUrlModulesRoot() . 'sv-geolocation/images/world-map.jpg';

if (version_compare(ITOP_DESIGN_LATEST_VERSION , 3.0) < 0)
{
$oPage->add(<<<HTML
<div class="dashlet-content">
<div id="{$sId}_panel" class="map_panel" style="display: {$sDisplaySearch};"><input id="{$sId}_address" type="text" /><button id="{$sId}_submit">{$sSearch}</button></div>
<div id="{$sId}" style="height: {$this->aProperties['height']}px; background: white url('{$sBackgroundUrl}') 50%/contain no-repeat;"></div>
</div>
$oBlock = UIContentBlockUIBlockFactory::MakeStandard(null, ["dashlet-content"]);
$oBlock->AddSubBlock(new Html(<<<HTML
<div id="{$sId}_panel" class="map_panel" style="display: {$sDisplaySearch};"><input id="{$sId}_address" type="text" /><button id="{$sId}_submit">{$sSearch}</button></div>
<div id="{$sId}" class="ibo-panel--body" style="height: {$this->aProperties['height']}px; background: #ffffff url('{$sBackgroundUrl}') 50%/contain no-repeat;"></div>
HTML
);
}
else
));

if ($bEditMode) return $oBlock;

$oFilter = DBObjectSearch::FromOQL($this->aProperties['query']);

$sCreateUrl = null;
if (UserRights::IsActionAllowed($oFilter->GetClass(), UR_ACTION_MODIFY))
{
$oBlock = UIContentBlockUIBlockFactory::MakeStandard(null, ["dashlet-content"]);
$oBlock->AddSubBlock(new Html(<<<HTML
<div id="{$sId}_panel" class="map_panel" style="display: {$sDisplaySearch};"><input id="{$sId}_address" type="text" /><button id="{$sId}_submit">{$sSearch}</button></div>
<div id="{$sId}" class="ibo-panel--body" style="height: {$this->aProperties['height']}px; background: #ffffff url('{$sBackgroundUrl}') 50%/contain no-repeat;"></div>
HTML
));
$sCreateUrl = sprintf(utils::GetAbsoluteUrlAppRoot() . 'pages/UI.php?operation=new&class=%s&default[%s]=', $oFilter->GetClass(), $this->aProperties['attribute']);
}

if ($bEditMode) return $oBlock;


$aDashletOptions = array(
'id' => $sId,
'id' => $sId,
'classLabel' => MetaModel::GetName($oFilter->GetClass()),
'createUrl' => $sCreateUrl,
'map' => array('center' => array('lat' => $iDefaultLat, 'lng' => $iDefaultLng), 'zoom' => $iZoom),
'locations' => array(),
'createUrl' => $sCreateUrl,
'center' => ['lat' => $iDefaultLat, 'lng' => $iDefaultLng],
'zoom' => $iZoom,
'locations' => [],
);

// Load objects
$oSet = new DBObjectSet($oFilter);
while ($oCurrObj = $oSet->Fetch())
{
if ($oCurrObj->Get($this->aProperties['attribute']))
{
$aDashletOptions['locations'][] = array(
'title' => $oCurrObj->GetName(),
'icon' => $oCurrObj->GetIcon(false),
'title' => $oCurrObj->GetName(),
'icon' => $oCurrObj->GetIcon(false),
'position' => $oCurrObj->Get($this->aProperties['attribute']),
'tooltip' => static::GetTooltip($oCurrObj),
'tooltip' => static::GetTooltip($oCurrObj),
);
}
}

// Make interactive
$oPage->add_linked_script(sprintf('https://maps.googleapis.com/maps/api/js?key=%s', $sApiKey));
$oPage->add_linked_script(utils::GetAbsoluteUrlModulesRoot().'sv-geolocation/js/google-maps-utils.js');
switch (utils::GetConfig()->GetModuleSetting('sv-geolocation', 'provider'))
{
case 'GoogleMaps':
list($sLang, $sRegion) = explode(' ', UserRights::GetUserLanguage(), 2);
$sLang = match (UserRights::GetUserLanguage())
{
'PT BR', 'ZH CN' => strtolower($sLang) . '-' . $sRegion,
default => strtolower($sLang),
};

$oPage->LinkScriptFromURI(sprintf('https://maps.googleapis.com/maps/api/js?key=%s&callback=$.noop&language=%s&libraries=marker', $sApiKey, $sLang));
$oPage->LinkScriptFromModule('sv-geolocation/js/google-maps-utils.js');
break;
case 'MapLibre':
case 'MapTiler':
case 'OpenStreetMap':
$aDashletOptions['style'] = AttributeGeolocation::GetStyle();

$oPage->LinkScriptFromURI('https://unpkg.com/maplibre-gl/dist/maplibre-gl.js');
$oPage->LinkStylesheetFromURI('https://unpkg.com/maplibre-gl/dist/maplibre-gl.css');
$oPage->LinkScriptFromModule('sv-geolocation/js/maplibre-utils.js');
break;
default:
return $oBlock;
}
$oPage->add_ready_script(sprintf('render_geomap(%s);', json_encode($aDashletOptions)));

return $oBlock;
}

/**
* Add properties fields
* @param DesignerForm $oForm
* @return mixed
* @inheritDoc
* @throws CoreException
*/
public function GetPropertiesFields(DesignerForm $oForm)
public function GetPropertiesFields(DesignerForm $oForm): void
{
$oHeightField = new DesignerIntegerField('height', Dict::S('UI:DashletGeoMap:Prop-Height'), $this->aProperties['height']);
$oHeightField->SetMandatory();
$oForm->AddField($oHeightField);

$oSearchField = new DesignerBooleanField('search', Dict::S('UI:DashletGeoMap:Prop-Search'), $this->aProperties['search']);
$oForm->AddField($oSearchField);

$oQueryField = new DesignerLongTextField('query', Dict::S('UI:DashletGeoMap:Prop-Query'), $this->aProperties['query']);
$oQueryField->SetMandatory();
$oForm->AddField($oQueryField);

try {

try
{
$sClass = $this->oModelReflection->GetQuery($this->aProperties['query'])->GetClass();
$oAttributeField = new DesignerComboField('attribute', Dict::S('UI:DashletGeoMap:Prop-Attribute'), $this->aProperties['attribute']);
$oAttributeField->SetAllowedValues(static::GetGeolocationAttributes($sClass));
Expand All @@ -150,21 +167,22 @@ public function GetPropertiesFields(DesignerForm $oForm)
$oForm->AddField($oAttributeField);
}
}

/**
* @param array $aValues
* @param array $aUpdatedFields
* @return Dashlet
* @inheritDoc
* @return GeoMap
*/
public function Update($aValues, $aUpdatedFields)
public function Update($aValues, $aUpdatedFields): GeoMap
{
if (in_array('query', $aUpdatedFields))
{
try {
try
{
$sCurrClass = $this->oModelReflection->GetQuery($aValues['query'])->GetClass();
$sPrevClass = $this->oModelReflection->GetQuery($this->aProperties['query'])->GetClass();

if ($sCurrClass != $sPrevClass) {

if ($sCurrClass != $sPrevClass)
{
$this->bFormRedrawNeeded = true;
}
}
Expand All @@ -173,46 +191,55 @@ public function Update($aValues, $aUpdatedFields)
$this->bFormRedrawNeeded = true;
}
}

return parent::Update($aValues, $aUpdatedFields);
}

/**
* Dashlet info
* @return array
* @return array{label: string, icon: string, description: string}
*/
public static function GetInfo()
public static function GetInfo(): array
{
return array(
'label' => Dict::S('UI:DashletGeoMap:Label', 'GeoMap'),
'icon' => 'env-'.MetaModel::GetEnvironment().'/sv-geolocation/images/geomap.png',
'label' => Dict::S('UI:DashletGeoMap:Label', 'GeoMap'),
'icon' => 'env-' . MetaModel::GetEnvironment() . '/sv-geolocation/images/geomap.png',
'description' => Dict::S('UI:DashletGeoMap:Description'),
);
}

protected static function GetTooltip(DBObject $oCurrObj)

/**
* @param DBObject $oCurrObj
* @return string
* @throws ArchivedObjectException
* @throws CoreException
* @throws DictExceptionMissingString
* @throws Exception
*/
protected static function GetTooltip(DBObject $oCurrObj): string
{
$sClass = get_class($oCurrObj);
$sTooltip = $oCurrObj->GetHyperlink().'<hr/>'.PHP_EOL;
$sTooltip .= '<table><tbody>'.PHP_EOL;
foreach(MetaModel::GetZListItems($sClass, 'list') as $sAttCode)
$sTooltip = $oCurrObj->GetHyperlink() . '<hr/>' . PHP_EOL;
$sTooltip .= '<table><tbody>' . PHP_EOL;
foreach (MetaModel::GetZListItems($sClass, 'list') as $sAttCode)
{
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$sTooltip .= '<tr><td>'.$oAttDef->GetLabel().':&nbsp;</td><td>'.$oCurrObj->GetAsHtml($sAttCode).'</td></tr>'.PHP_EOL;
$sTooltip .= '<tr><td>' . $oAttDef->GetLabel() . ':&nbsp;</td><td>' . $oCurrObj->GetAsHtml($sAttCode) . '</td></tr>' . PHP_EOL;
}
$sTooltip .= '</tbody></table>';

return $sTooltip;
}

/**
* @param string $sClass
* @return array
* @return array{string, string}
* @throws CoreException
*/
protected static function GetGeolocationAttributes($sClass)
protected static function GetGeolocationAttributes(string $sClass): array
{
if (isset(static::$aAttributeList[$sClass])) return static::$aAttributeList[$sClass];

$aAttributes = array();
foreach (MetaModel::ListAttributeDefs($sClass) as $sAttribute => $oAttributeDef)
{
Expand All @@ -222,7 +249,7 @@ protected static function GetGeolocationAttributes($sClass)
}
}
static::$aAttributeList[$sClass] = $aAttributes;

return $aAttributes;
}
}
}
Loading