8000 Add MapLibre provider by Hipska · Pull Request #9 · Super-Visions/sv-geolocation · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
8000

Add MapLibre provider #9

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

Merged
merged 28 commits into from
Jun 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
7d4c4dc
Initial rework with fixed MapTiler style
Hipska Jan 22, 2025
ea14c29
Implement MapLibre provider for interactive form using MapTiler style
Hipska Jan 22, 2025
ac0b72d
Mimic dashlet behaviour to Google Maps provider
Hipska Jan 22, 2025
f233700
Restore GoogleMaps provider dashlet functionality
Hipska Jan 22, 2025
ff2b3e9
Removed unused marker popup
Hipska Jan 22, 2025
a911a5e
Revert change to short syntax
Hipska Jan 22, 2025
1aa2fb5
Update copyright
Hipska Jan 22, 2025
72a55a4
Typo
Hipska Jan 22, 2025
b4ea33e
Better copyright updates
Hipska Jan 22, 2025
46a96a6
Documentation update
Hipska Jan 22, 2025
3382166
Code style cleanup
Hipska Jan 23, 2025
d6b682a
Reuse same language selection on GoogleMaps dashlet
Hipska Jan 23, 2025
4af4120
Don't use hard-coded MapTiler style
Hipska Jan 23, 2025
bfe5b07
Code style cleanup
Hipska Jan 23, 2025
1c52e9e
Hide search for MapLibre
Hipska Jan 23, 2025
939b39c
Also modernise Google Maps code
Hipska Jan 24, 2025
5316b26
Allow disable static map by setting the value to empty string
Hipska Jan 24, 2025
8000 bfbbfb0
Merge branch 'master' into feature/maplibre
Hipska Apr 29, 2025
a7c95da
Remove unused argument
Hipska May 21, 2025
dc68673
Use class icon for the features
Hipska May 21, 2025
c38ba52
Change mouse cursor on mouseover
Hipska May 21, 2025
87d05d1
Use summary card where possible
Hipska May 21, 2025
4b66c77
Allow overlapping to see duplicate objects
Hipska May 22, 2025
a15ec84
Variable text positioning around the icon
Hipska May 28, 2025
3ad9a6d
Resize all icons to 32px
Hipska May 28, 2025
db88fe6
Add popupdelay when the object has a summary card
Hipska May 28, 2025
9e13364
Add MapQuest as interactive provider
Hipska Jun 6, 2025
0c22c55
Add to changelog
Hipska Jun 6, 2025
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- German translation, thanks to [@rudnerbjoern](https://github.com/rudnerbjoern).
- Added new MapLibre providers for interactive maps, which required some refactoring.

### Fixed

Expand Down
31 changes: 22 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,13 @@ 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`
* `MapQuest`

### api_key

Expand All @@ -47,13 +48,25 @@ 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`
`https://www.mapquestapi.com/staticmap/v5/map?locations=%f,%f&size=%d,%d@2x&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
* MapQuest

### default_latitude

Expand Down Expand Up @@ -118,7 +131,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
208 changes: 127 additions & 81 deletions geomap.class.inc.php
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
<?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;
use Combodo\iTop\Service\Router\Router;
use Combodo\iTop\Service\SummaryCard\SummaryCardService;

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 +29,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 +58,107 @@ 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(),
'classIcon' => MetaModel::GetClassIcon($oFilter->GetClass(), false),
'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),
'summary' => static::GetSummaryCard($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':
case 'MapQuest':
$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->LinkScriptFromURI('https://rbrundritt.github.io/maplibre-gl-svg/dist/maplibre-gl-svg.min.js');
$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 +173,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 +197,68 @@ 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;
}


/**
* @return string The URL to retrieve the summary card
* @throws Exception
*/
protected static function GetSummaryCard(DBObject $oCurrObj): string
{
$sClass = get_class($oCurrObj);
if (!SummaryCardService::IsAllowedForClass($sClass)) return '';

$oRouter = Router::GetInstance();
return $oRouter->GenerateUrl("object.summary", ["obj_class" => $sClass, "obj_key" => $oCurrObj->GetKey()]);
}

/**
* @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 +268,7 @@ protected static function GetGeolocationAttributes($sClass)
}
}
static::$aAttributeList[$sClass] = $aAttributes;

return $aAttributes;
}
}
}
Loading
0