1<?php 2 3/* 4 * Copyright (c) 2008-2023 Mark C. Prins <mprins@users.sf.net> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 * 18 * @phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps 19 */ 20use dokuwiki\Extension\SyntaxPlugin; 21use geoPHP\Geometry\Point; 22use dokuwiki\Logger; 23 24/** 25 * DokuWiki Plugin openlayersmap (Syntax Component). 26 * Provides for display of an OpenLayers based map in a wiki page. 27 * 28 * @author Mark Prins 29 */ 30class syntax_plugin_openlayersmap_olmap extends SyntaxPlugin 31{ 32 /** 33 * defaults of the known attributes of the olmap tag. 34 */ 35 private $dflt = ['id' => 'olmap', 'width' => '550px', 'height' => '450px', 'lat' => 50.0, 'lon' => 5.1, 'zoom' => 12, 'autozoom' => 1, 'controls' => true, 'baselyr' => 'OpenStreetMap', 'gpxfile' => '', 'kmlfile' => '', 'geojsonfile' => '', 'summary' => '']; 36 37 /** 38 * 39 * @see DokuWiki_Syntax_Plugin::getType() 40 */ 41 public function getType(): string 42 { 43 return 'substition'; 44 } 45 46 /** 47 * 48 * @see DokuWiki_Syntax_Plugin::getPType() 49 */ 50 public function getPType(): string 51 { 52 return 'block'; 53 } 54 55 /** 56 * 57 * @see Doku_Parser_Mode::getSort() 58 */ 59 public function getSort(): int 60 { 61 return 901; 62 } 63 64 /** 65 * 66 * @see Doku_Parser_Mode::connectTo() 67 */ 68 public function connectTo($mode) 69 { 70 $this->Lexer->addSpecialPattern( 71 '<olmap ?[^>\n]*>.*?</olmap>', 72 $mode, 73 'plugin_openlayersmap_olmap' 74 ); 75 } 76 77 /** 78 * 79 * @see DokuWiki_Syntax_Plugin::handle() 80 */ 81 public function handle($match, $state, $pos, Doku_Handler $handler): array 82 { 83 // break matched data into its components 84 $_tag = explode('>', substr($match, 7, -9), 2); 85 $str_params = $_tag[0]; 86 if (array_key_exists(1, $_tag)) { 87 $str_points = $_tag[1]; 88 } else { 89 $str_points = ''; 90 } 91 // get the lat/lon for adding them to the metadata (used by geotag) 92 preg_match('(lat[:|=]\"-?\d*\.?\d*\")', $match, $mainLat); 93 preg_match('(lon[:|=]\"-?\d*\.?\d*\")', $match, $mainLon); 94 $mainLat = substr($mainLat [0], 5, -1); 95 $mainLon = substr($mainLon [0], 5, -1); 96 if (!is_numeric($mainLat)) { 97 $mainLat = $this->dflt ['lat']; 98 } 99 if (!is_numeric($mainLon)) { 100 $mainLon = $this->dflt ['lon']; 101 } 102 103 $gmap = $this->extractParams($str_params); 104 $overlay = $this->extractPoints($str_points); 105 $_firstimageID = ''; 106 107 $_nocache = false; 108 // choose maptype based on the specified tag 109 $imgUrl = "{{"; 110 if (stripos($gmap ['baselyr'], 'google') !== false) { 111 // Google 112 $imgUrl .= $this->getGoogle($gmap, $overlay); 113 $imgUrl .= "&.png"; 114 } elseif (stripos($gmap ['baselyr'], 'bing') !== false) { 115 // Bing 116 if (!$this->getConf('bingAPIKey')) { 117 // in case there is no Bing api key we'll use OSM 118 $_firstimageID = $this->getStaticOSM($gmap, $overlay); 119 $imgUrl .= $_firstimageID; 120 if ($this->getConf('optionStaticMapGenerator') == 'remote') { 121 $imgUrl .= "&.png"; 122 } 123 } else { 124 // seems that Bing doesn't like the DW client, turn off caching 125 $_nocache = true; 126 $imgUrl .= $this->getBing($gmap, $overlay) . "&.png"; 127 } 128 /* elseif (stripos ( $gmap ['baselyr'], 'mapquest' ) !== false) { 129 // MapQuest 130 if (! $this->getConf ( 'mapquestAPIKey' )) { 131 // no API key for MapQuest, use OSM 132 $_firstimageID = $this->getStaticOSM ( $gmap, $overlay ); 133 $imgUrl .= $_firstimageID; 134 if ($this->getConf ( 'optionStaticMapGenerator' ) == 'remote') { 135 $imgUrl .= "&.png"; 136 } 137 } else { 138 $imgUrl .= $this->_getMapQuest ( $gmap, $overlay ); 139 $imgUrl .= "&.png"; 140 } 141 } */ 142 } else { 143 // default OSM 144 $_firstimageID = $this->getStaticOSM($gmap, $overlay); 145 $imgUrl .= $_firstimageID; 146 if ($this->getConf('optionStaticMapGenerator') == 'remote') { 147 $imgUrl .= "&.png"; 148 } 149 } 150 151 // append dw p_render specific params and render 152 $imgUrl .= "?" . str_replace("px", "", $gmap ['width']) . "x" 153 . str_replace("px", "", $gmap ['height']); 154 $imgUrl .= "&nolink"; 155 156 // add nocache option for selected services 157 if ($_nocache) { 158 $imgUrl .= "&nocache"; 159 } 160 161 $imgUrl .= " |" . $gmap ['summary'] . " }}"; 162 163 $mapid = $gmap ['id']; 164 // create a javascript parameter string for the map 165 $param = ''; 166 foreach ($gmap as $key => $val) { 167 $param .= is_numeric($val) ? "$key: $val, " : "$key: '" . hsc($val) . "', "; 168 } 169 if (!empty($param)) { 170 $param = substr($param, 0, -2); 171 } 172 unset($gmap ['id']); 173 174 // create a javascript serialisation of the point data 175 $poi = ''; 176 $poitable = ''; 177 $rowId = 0; 178 if ($overlay !== []) { 179 foreach ($overlay as $data) { 180 [$lat, $lon, $text, $angle, $opacity, $img] = $data; 181 $rowId++; 182 $poi .= ", {lat:$lat,lon:$lon,txt:'$text',angle:$angle,opacity:$opacity,img:'$img',rowId: $rowId}"; 183 184 if ($this->getConf('displayformat') === 'DMS') { 185 $lat = $this->convertLat($lat); 186 $lon = $this->convertLon($lon); 187 } else { 188 $lat .= 'º'; 189 $lon .= 'º'; 190 } 191 192 $poitable .= ' 193 <tr> 194 <td class="rowId">' . $rowId . '</td> 195 <td class="icon"><img src="' . DOKU_BASE . 'lib/plugins/openlayersmap/icons/' . $img . '" alt="' 196 . substr($img, 0, -4) . $this->getlang('alt_legend_poi') . '" /></td> 197 <td class="lat" title="' . $this->getLang('olmapPOIlatTitle') . '">' . $lat . '</td> 198 <td class="lon" title="' . $this->getLang('olmapPOIlonTitle') . '">' . $lon . '</td> 199 <td class="txt">' . $text . '</td> 200 </tr>'; 201 } 202 $poi = substr($poi, 2); 203 } 204 if (!empty($gmap ['kmlfile'])) { 205 $poitable .= ' 206 <tr> 207 <td class="rowId"><img src="' . DOKU_BASE 208 . 'lib/plugins/openlayersmap/toolbar/kml_file.png" alt="KML file" /></td> 209 <td class="icon"><img src="' . DOKU_BASE . 'lib/plugins/openlayersmap/toolbar/kml_line.png" alt="' 210 . $this->getlang('alt_legend_kml') . '" /></td> 211 <td class="txt" colspan="3">KML track: ' . $this->getFileName($gmap ['kmlfile']) . '</td> 212 </tr>'; 213 } 214 if (!empty($gmap ['gpxfile'])) { 215 $poitable .= ' 216 <tr> 217 <td class="rowId"><img src="' . DOKU_BASE 218 . 'lib/plugins/openlayersmap/toolbar/gpx_file.png" alt="GPX file" /></td> 219 <td class="icon"><img src="' . DOKU_BASE 220 . 'lib/plugins/openlayersmap/toolbar/gpx_line.png" alt="' 221 . $this->getlang('alt_legend_gpx') . '" /></td> 222 <td class="txt" colspan="3">GPX track: ' . $this->getFileName($gmap ['gpxfile']) . '</td> 223 </tr>'; 224 } 225 if (!empty($gmap ['geojsonfile'])) { 226 $poitable .= ' 227 <tr> 228 <td class="rowId"><img src="' . DOKU_BASE 229 . 'lib/plugins/openlayersmap/toolbar/geojson_file.png" alt="GeoJSON file" /></td> 230 <td class="icon"><img src="' . DOKU_BASE 231 . 'lib/plugins/openlayersmap/toolbar/geojson_line.png" alt="' 232 . $this->getlang('alt_legend_geojson') . '" /></td> 233 <td class="txt" colspan="3">GeoJSON track: ' . $this->getFileName($gmap ['geojsonfile']) . '</td> 234 </tr>'; 235 } 236 237 $autozoom = empty($gmap ['autozoom']) ? $this->getConf('autoZoomMap') : $gmap ['autozoom']; 238 $js = "{mapOpts: {" . $param . ", displayformat: '" . $this->getConf('displayformat') 239 . "', autozoom: " . $autozoom . "}, poi: [$poi]};"; 240 // unescape the json 241 $poitable = stripslashes($poitable); 242 243 return [$mapid, $js, $mainLat, $mainLon, $poitable, $gmap ['summary'], $imgUrl, $_firstimageID]; 244 } 245 246 /** 247 * extract parameters for the map from the parameter string 248 * 249 * @param string $str_params 250 * string of key="value" pairs 251 * @return array associative array of parameters key=>value 252 */ 253 private function extractParams(string $str_params): array 254 { 255 $param = []; 256 preg_match_all('/(\w*)="(.*?)"/us', $str_params, $param, PREG_SET_ORDER); 257 // parse match for instructions, break into key value pairs 258 $gmap = $this->dflt; 259 foreach ($gmap as $key => &$value) { 260 $defval = $this->getConf('default_' . $key); 261 if ($defval !== '') { 262 $value = $defval; 263 } 264 } 265 unset($value); 266 foreach ($param as $kvpair) { 267 [$match, $key, $val] = $kvpair; 268 $key = strtolower($key); 269 if (isset($gmap [$key])) { 270 if ($key == 'summary') { 271 // preserve case for summary field 272 $gmap [$key] = $val; 273 } elseif ($key == 'id') { 274 // preserve case for id field 275 $gmap [$key] = $val; 276 } else { 277 $gmap [$key] = strtolower($val); 278 } 279 } 280 } 281 return $gmap; 282 } 283 284 /** 285 * extract overlay points for the map from the wiki syntax data 286 * 287 * @param string $str_points 288 * multi-line string of lat,lon,text triplets 289 * @return array multi-dimensional array of lat,lon,text triplets 290 */ 291 private function extractPoints(string $str_points): array 292 { 293 $point = []; 294 // preg_match_all('/^([+-]?[0-9].*?),\s*([+-]?[0-9].*?),(.*?),(.*?),(.*?),(.*)$/um', 295 // $str_points, $point, PREG_SET_ORDER); 296 /* 297 * group 1: ([+-]?[0-9]+(?:\.[0-9]*)?) 298 * group 2: ([+-]?[0-9]+(?:\.[0-9]*)?) 299 * group 3: (.*?) 300 * group 4: (.*?) 301 * group 5: (.*?) 302 * group 6: (.*) 303 */ 304 preg_match_all( 305 '/^([+-]?[0-9]+(?:\.[0-9]*)?),\s*([+-]?[0-9]+(?:\.[0-9]*)?),(.*?),(.*?),(.*?),(.*)$/um', 306 $str_points, 307 $point, 308 PREG_SET_ORDER 309 ); 310 // create poi array 311 $overlay = []; 312 foreach ($point as $pt) { 313 [$match, $lat, $lon, $angle, $opacity, $img, $text] = $pt; 314 $lat = is_numeric($lat) ? $lat : 0; 315 $lon = is_numeric($lon) ? $lon : 0; 316 $angle = is_numeric($angle) ? $angle : 0; 317 $opacity = is_numeric($opacity) ? $opacity : 0.8; 318 // TODO validate using exist & set up default img? 319 $img = trim($img); 320 $text = p_get_instructions($text); 321 // dbg ( $text ); 322 $text = p_render("xhtml", $text, $info); 323 // dbg ( $text ); 324 $text = addslashes(str_replace("\n", "", $text)); 325 $overlay [] = [$lat, $lon, $text, $angle, $opacity, $img]; 326 } 327 return $overlay; 328 } 329 330 /** 331 * Create a Google maps static image url w/ the poi. 332 * 333 * @param array $gmap 334 * @param array $overlay 335 */ 336 private function getGoogle(array $gmap, array $overlay): string 337 { 338 $sUrl = $this->getConf('iconUrlOverload'); 339 if (!$sUrl) { 340 $sUrl = DOKU_URL; 341 } 342 switch ($gmap ['baselyr']) { 343 case 'google hybrid': 344 $maptype = 'hybrid'; 345 break; 346 case 'google sat': 347 $maptype = 'satellite'; 348 break; 349 case 'terrain': 350 case 'google relief': 351 $maptype = 'terrain'; 352 break; 353 case 'google road': 354 default: 355 $maptype = 'roadmap'; 356 break; 357 } 358 // TODO maybe use viewport / visible instead of center/zoom, 359 // see: https://developers.google.com/maps/documentation/staticmaps/index#Viewports 360 // http://maps.google.com/maps/api/staticmap?center=51.565690,5.456756&zoom=16&size=600x400&markers=icon:http://wild-water.nl/dokuwiki/lib/plugins/openlayersmap/icons/marker.png|label:1|51.565690,5.456756&markers=icon:http://wild-water.nl/dokuwiki/lib/plugins/openlayersmap/icons/marker-blue.png|51.566197,5.458966|label:2&markers=icon:http://wild-water.nl/dokuwiki/lib/plugins/openlayersmap/icons/parking.png|51.567177,5.457909|label:3&markers=icon:http://wild-water.nl/dokuwiki/lib/plugins/openlayersmap/icons/parking.png|51.566283,5.457330|label:4&markers=icon:http://wild-water.nl/dokuwiki/lib/plugins/openlayersmap/icons/parking.png|51.565630,5.457695|label:5&sensor=false&format=png&maptype=roadmap 361 $imgUrl = "https://maps.googleapis.com/maps/api/staticmap?"; 362 $imgUrl .= "&size=" . str_replace("px", "", $gmap ['width']) . "x" 363 . str_replace("px", "", $gmap ['height']); 364 //if (!$this->getConf( 'autoZoomMap')) { // no need for center & zoom params } 365 $imgUrl .= "¢er=" . $gmap ['lat'] . "," . $gmap ['lon']; 366 // max is 21 (== building scale), but that's overkill.. 367 if ($gmap ['zoom'] > 17) { 368 $imgUrl .= "&zoom=17"; 369 } else { 370 $imgUrl .= "&zoom=" . $gmap ['zoom']; 371 } 372 if ($overlay !== []) { 373 $rowId = 0; 374 foreach ($overlay as $data) { 375 [$lat, $lon, $text, $angle, $opacity, $img] = $data; 376 $imgUrl .= "&markers=icon%3a" . $sUrl . "lib/plugins/openlayersmap/icons/" . $img . "%7c" 377 . $lat . "," . $lon . "%7clabel%3a" . ++$rowId; 378 } 379 } 380 $imgUrl .= "&format=png&maptype=" . $maptype; 381 global $conf; 382 $imgUrl .= "&language=" . $conf ['lang']; 383 if ($this->getConf('googleAPIkey')) { 384 $imgUrl .= "&key=" . $this->getConf('googleAPIkey'); 385 } 386 return $imgUrl; 387 } 388 389 /** 390 * Create a MapQuest static map API image url. 391 * 392 * @param array $gmap 393 * @param array $overlay 394 */ 395 /* 396 private function _getMapQuest($gmap, $overlay) { 397 $sUrl = $this->getConf ( 'iconUrlOverload' ); 398 if (! $sUrl) { 399 $sUrl = DOKU_URL; 400 } 401 switch ($gmap ['baselyr']) { 402 case 'mapquest hybrid' : 403 $maptype = 'hyb'; 404 break; 405 case 'mapquest sat' : 406 // because sat coverage is very limited use 'hyb' instead of 'sat' so we don't get a blank map 407 $maptype = 'hyb'; 408 break; 409 case 'mapquest road' : 410 default : 411 $maptype = 'map'; 412 break; 413 } 414 $imgUrl = "http://open.mapquestapi.com/staticmap/v4/getmap?declutter=true&"; 415 if (count ( $overlay ) < 1) { 416 $imgUrl .= "?center=" . $gmap ['lat'] . "," . $gmap ['lon']; 417 // max level for mapquest is 16 418 if ($gmap ['zoom'] > 16) { 419 $imgUrl .= "&zoom=16"; 420 } else { 421 $imgUrl .= "&zoom=" . $gmap ['zoom']; 422 } 423 } 424 // use bestfit instead of center/zoom, needs upperleft/lowerright corners 425 // $bbox=$this->calcBBOX($overlay, $gmap['lat'], $gmap['lon']); 426 // $imgUrl .= "bestfit=".$bbox['minlat'].",".$bbox['maxlon'].",".$bbox['maxlat'].",".$bbox['minlon']; 427 428 // TODO declutter option works well for square maps but not for rectangular, maybe compensate for that 429 // or compensate the mbr.. 430 431 $imgUrl .= "&size=" . str_replace ( "px", "", $gmap ['width'] ) . "," . str_replace ("px","",$gmap['height']); 432 433 // TODO mapquest allows using one image url with a multiplier $NUMBER eg: 434 // $NUMBER = 2 435 // $imgUrl .= DOKU_URL."/".DOKU_PLUGIN."/".getPluginName()."/icons/".$img.",$NUMBER,C," 436 // .$lat1.",".$lon1.",0,0,0,0,C,".$lat2.",".$lon2.",0,0,0,0"; 437 if (! empty ( $overlay )) { 438 $imgUrl .= "&xis="; 439 foreach ( $overlay as $data ) { 440 list ( $lat, $lon, $text, $angle, $opacity, $img ) = $data; 441 // $imgUrl .= $sUrl."lib/plugins/openlayersmap/icons/".$img.",1,C,".$lat.",".$lon.",0,0,0,0,"; 442 $imgUrl .= $sUrl . "lib/plugins/openlayersmap/icons/" . $img . ",1,C," . $lat . "," . $lon . ","; 443 } 444 $imgUrl = substr ( $imgUrl, 0, - 1 ); 445 } 446 $imgUrl .= "&imageType=png&type=" . $maptype; 447 $imgUrl .= "&key=".$this->getConf ( 'mapquestAPIKey' ); 448 return $imgUrl; 449 } 450 */ 451 452 /** 453 * Create a static OSM map image url w/ the poi from http://staticmap.openstreetmap.de (staticMapLite) 454 * use http://staticmap.openstreetmap.de "staticMapLite" or a local version 455 * 456 * @param array $gmap 457 * @param array $overlay 458 * 459 * @return false|string 460 * @todo implementation for http://ojw.dev.openstreetmap.org/StaticMapDev/ 461 */ 462 private function getStaticOSM(array $gmap, array $overlay) 463 { 464 global $conf; 465 466 if ($this->getConf('optionStaticMapGenerator') === 'local') { 467 // using local basemap composer 468 if (($myMap = plugin_load('helper', 'openlayersmap_staticmap')) === null) { 469 Logger::error( 470 'openlayersmap_staticmap plugin is not available for use.', 471 $myMap 472 ); 473 } 474 if (($geophp = plugin_load('helper', 'geophp')) === null) { 475 Logger::debug('geophp plugin is not available for use.', $geophp); 476 } 477 $size = str_replace("px", "", $gmap ['width']) . "x" 478 . str_replace("px", "", $gmap ['height']); 479 480 $markers = []; 481 if ($overlay !== []) { 482 foreach ($overlay as $data) { 483 [$lat, $lon, $text, $angle, $opacity, $img] = $data; 484 $iconStyle = substr($img, 0, -4); 485 $markers [] = ['lat' => $lat, 'lon' => $lon, 'type' => $iconStyle]; 486 } 487 } 488 489 $apikey = ''; 490 switch ($gmap ['baselyr']) { 491 case 'mapnik': 492 case 'openstreetmap': 493 $maptype = 'openstreetmap'; 494 break; 495 case 'transport': 496 $maptype = 'transport'; 497 $apikey = '?apikey=' . $this->getConf('tfApiKey'); 498 break; 499 case 'landscape': 500 $maptype = 'landscape'; 501 $apikey = '?apikey=' . $this->getConf('tfApiKey'); 502 break; 503 case 'outdoors': 504 $maptype = 'outdoors'; 505 $apikey = '?apikey=' . $this->getConf('tfApiKey'); 506 break; 507 case 'cycle map': 508 $maptype = 'cycle'; 509 $apikey = '?apikey=' . $this->getConf('tfApiKey'); 510 break; 511 case 'hike and bike map': 512 $maptype = 'hikeandbike'; 513 break; 514 case 'mapquest hybrid': 515 case 'mapquest road': 516 case 'mapquest sat': 517 $maptype = 'mapquest'; 518 break; 519 default: 520 $maptype = ''; 521 break; 522 } 523 524 $result = $myMap->getMap( 525 $gmap ['lat'], 526 $gmap ['lon'], 527 $gmap ['zoom'], 528 $size, 529 $maptype, 530 $markers, 531 $gmap ['gpxfile'], 532 $gmap ['kmlfile'], 533 $gmap ['geojsonfile'], 534 $apikey 535 ); 536 } else { 537 // using external basemap composer 538 539 // https://staticmap.openstreetmap.de/staticmap.php?center=47.000622235634,10 540 //.117187497601&zoom=5&size=500x350 541 // &markers=48.999812532766,8.3593749976708,lightblue1|43.154850037315,17.499999997306, 542 // lightblue1|49.487527053077,10.820312497573,ltblu-pushpin|47.951071133739,15.917968747369, 543 // ol-marker|47.921629720114,18.027343747285,ol-marker-gold|47.951071133739,19.257812497236, 544 // ol-marker-blue|47.180141361692,19.257812497236,ol-marker-green 545 $imgUrl = "https://staticmap.openstreetmap.de/staticmap.php"; 546 $imgUrl .= "?center=" . $gmap ['lat'] . "," . $gmap ['lon']; 547 $imgUrl .= "&size=" . str_replace("px", "", $gmap ['width']) . "x" 548 . str_replace("px", "", $gmap ['height']); 549 550 if ($gmap ['zoom'] > 16) { 551 // actually this could even be 18, but that seems overkill 552 $imgUrl .= "&zoom=16"; 553 } else { 554 $imgUrl .= "&zoom=" . $gmap ['zoom']; 555 } 556 557 if ($overlay !== []) { 558 $rowId = 0; 559 $imgUrl .= "&markers="; 560 foreach ($overlay as $data) { 561 [$lat, $lon, $text, $angle, $opacity, $img] = $data; 562 $rowId++; 563 $iconStyle = "lightblue$rowId"; 564 $imgUrl .= "$lat,$lon,$iconStyle%7c"; 565 } 566 $imgUrl = substr($imgUrl, 0, -3); 567 } 568 569 $result = $imgUrl; 570 } 571 return $result; 572 } 573 574 /** 575 * Create a Bing maps static image url w/ the poi. 576 * 577 * @param array $gmap 578 * @param array $overlay 579 */ 580 private function getBing(array $gmap, array $overlay): string 581 { 582 switch ($gmap ['baselyr']) { 583 case 've hybrid': 584 case 'bing hybrid': 585 $maptype = 'AerialWithLabels'; 586 break; 587 case 've sat': 588 case 'bing sat': 589 $maptype = 'Aerial'; 590 break; 591 case 've normal': 592 case 've road': 593 case 've': 594 case 'bing road': 595 default: 596 $maptype = 'Road'; 597 break; 598 } 599 $imgUrl = "https://dev.virtualearth.net/REST/v1/Imagery/Map/" . $maptype;// . "/"; 600 if ($this->getConf('autoZoomMap')) { 601 $bbox = $this->calcBBOX($overlay, $gmap ['lat'], $gmap ['lon']); 602 //$imgUrl .= "?ma=" . $bbox ['minlat'] . "," . $bbox ['minlon'] . "," 603 // . $bbox ['maxlat'] . "," . $bbox ['maxlon']; 604 $imgUrl .= "?ma=" . $bbox ['minlat'] . "%2C" . $bbox ['minlon'] . "%2C" . $bbox ['maxlat'] 605 . "%2C" . $bbox ['maxlon']; 606 $imgUrl .= "&dcl=1"; 607 } 608 if (strpos($imgUrl, "?") === false) 609 $imgUrl .= "?"; 610 611 //$imgUrl .= "&ms=" . str_replace ( "px", "", $gmap ['width'] ) . "," 612 // . str_replace ( "px", "", $gmap ['height'] ); 613 $imgUrl .= "&ms=" . str_replace("px", "", $gmap ['width']) . "%2C" 614 . str_replace("px", "", $gmap ['height']); 615 $imgUrl .= "&key=" . $this->getConf('bingAPIKey'); 616 if ($overlay !== []) { 617 $rowId = 0; 618 foreach ($overlay as $data) { 619 [$lat, $lon, $text, $angle, $opacity, $img] = $data; 620 // TODO icon style lookup, see: http://msdn.microsoft.com/en-us/library/ff701719.aspx for iconStyle 621 $iconStyle = 32; 622 $rowId++; 623 // NOTE: the max number of pushpins is 18! or we have to use POST 624 // (http://msdn.microsoft.com/en-us/library/ff701724.aspx) 625 if ($rowId == 18) { 626 break; 627 } 628 //$imgUrl .= "&pp=$lat,$lon;$iconStyle;$rowId"; 629 $imgUrl .= "&pp=$lat%2C$lon%3B$iconStyle%3B$rowId"; 630 } 631 } 632 global $conf; 633 $imgUrl .= "&fmt=png"; 634 $imgUrl .= "&c=" . $conf ['lang']; 635 return $imgUrl; 636 } 637 638 /** 639 * Calculate the minimum bbox for a start location + poi. 640 * 641 * @param array $overlay 642 * multi-dimensional array of array($lat, $lon, $text, $angle, $opacity, $img) 643 * @param float $lat 644 * latitude for map center 645 * @param float $lon 646 * longitude for map center 647 * @return array :float array describing the mbr and center point 648 */ 649 private function calcBBOX(array $overlay, float $lat, float $lon): array 650 { 651 $lats = [$lat]; 652 $lons = [$lon]; 653 foreach ($overlay as $data) { 654 [$lat, $lon, $text, $angle, $opacity, $img] = $data; 655 $lats [] = $lat; 656 $lons [] = $lon; 657 } 658 sort($lats); 659 sort($lons); 660 // TODO: make edge/wrap around cases work 661 $centerlat = $lats [0] + ($lats [count($lats) - 1] - $lats [0]); 662 $centerlon = $lons [0] + ($lons [count($lats) - 1] - $lons [0]); 663 return ['minlat' => $lats [0], 'minlon' => $lons [0], 'maxlat' => $lats [count($lats) - 1], 'maxlon' => $lons [count($lats) - 1], 'centerlat' => $centerlat, 'centerlon' => $centerlon]; 664 } 665 666 /** 667 * convert latitude in decimal degrees to DMS+hemisphere. 668 * 669 * @param float $decimaldegrees 670 * @todo move this into a shared library 671 */ 672 private function convertLat(float $decimaldegrees): string 673 { 674 if (strpos($decimaldegrees, '-') !== false) { 675 $latPos = "S"; 676 } else { 677 $latPos = "N"; 678 } 679 $dms = $this->convertDDtoDMS(abs($decimaldegrees)); 680 return hsc($dms . $latPos); 681 } 682 683 /** 684 * Convert decimal degrees to degrees, minutes, seconds format 685 * 686 * @param float $decimaldegrees 687 * @return string dms 688 * @todo move this into a shared library 689 */ 690 private function convertDDtoDMS(float $decimaldegrees): string 691 { 692 $dms = floor($decimaldegrees); 693 $secs = ($decimaldegrees - $dms) * 3600; 694 $min = floor($secs / 60); 695 $sec = round($secs - ($min * 60), 3); 696 $dms .= 'º' . $min . '\'' . $sec . '"'; 697 return $dms; 698 } 699 700 /** 701 * convert longitude in decimal degrees to DMS+hemisphere. 702 * 703 * @param float $decimaldegrees 704 * @todo move this into a shared library 705 */ 706 private function convertLon(float $decimaldegrees): string 707 { 708 if (strpos($decimaldegrees, '-') !== false) { 709 $lonPos = "W"; 710 } else { 711 $lonPos = "E"; 712 } 713 $dms = $this->convertDDtoDMS(abs($decimaldegrees)); 714 return hsc($dms . $lonPos); 715 } 716 717 /** 718 * Figures out the base filename of a media path. 719 * 720 * @param string $mediaLink 721 */ 722 private function getFileName(string $mediaLink): string 723 { 724 $mediaLink = str_replace('[[', '', $mediaLink); 725 $mediaLink = str_replace(']]', '', $mediaLink); 726 $mediaLink = substr($mediaLink, 0, -4); 727 728 $parts = explode(':', $mediaLink); 729 $mediaLink = end($parts); 730 return str_replace('_', ' ', $mediaLink); 731 } 732 733 /** 734 * 735 * @see DokuWiki_Syntax_Plugin::render() 736 */ 737 public function render($format, Doku_Renderer $renderer, $data): bool 738 { 739 // set to true after external scripts tags are written 740 static $initialised = false; 741 // incremented for each map tag in the page source so we can keep track of each map in this page 742 static $mapnumber = 0; 743 744 [$mapid, $param, $mainLat, $mainLon, $poitable, $poitabledesc, $staticImgUrl, $_firstimage] = $data; 745 746 if ($format === 'xhtml') { 747 $olscript = ''; 748 $stadiaEnable = $this->getConf('enableStadia'); 749 $osmEnable = $this->getConf('enableOSM'); 750 $enableBing = $this->getConf('enableBing'); 751 752 $scriptEnable = ''; 753 if (!$initialised) { 754 $initialised = true; 755 // render necessary script tags only once 756 $olscript = '<script defer="defer" src="' . DOKU_BASE . 'lib/plugins/openlayersmap/ol/ol.js"></script> 757<script defer="defer" src="' . DOKU_BASE . 'lib/plugins/openlayersmap/ol/ol-layerswitcher.js"></script>'; 758 759 $scriptEnable = '<script defer="defer" src="data:text/javascript;base64,'; 760 $scriptSrc = $olscript ? 'const olEnable=true;' : 'const olEnable=false;'; 761 $scriptSrc .= 'const osmEnable=' . ($osmEnable ? 'true' : 'false') . ';'; 762 $scriptSrc .= 'const stadiaEnable=' . ($stadiaEnable ? 'true' : 'false') . ';'; 763 $scriptSrc .= 'const bEnable=' . ($enableBing ? 'true' : 'false') . ';'; 764 $scriptSrc .= 'const bApiKey="' . $this->getConf('bingAPIKey') . '";'; 765 $scriptSrc .= 'const tfApiKey="' . $this->getConf('tfApiKey') . '";'; 766 $scriptSrc .= 'const gApiKey="' . $this->getConf('googleAPIkey') . '";'; 767 $scriptSrc .= 'olMapData = []; let olMaps = {}; let olMapOverlays = {};'; 768 $scriptEnable .= base64_encode($scriptSrc); 769 $scriptEnable .= '"></script>'; 770 } 771 $renderer->doc .= "$olscript\n$scriptEnable"; 772 $renderer->doc .= '<div class="olMapHelp">' . $this->locale_xhtml("help") . '</div>'; 773 if ($this->getConf('enableA11y')) { 774 $renderer->doc .= '<div id="' . $mapid . '-static" class="olStaticMap">' 775 . p_render($format, p_get_instructions($staticImgUrl), $info) . '</div>'; 776 } 777 $renderer->doc .= '<div id="' . $mapid . '-clearer" class="clearer"><p> </p></div>'; 778 if ($this->getConf('enableA11y')) { 779 // render a table of the POI for the print and a11y presentation, it is hidden using javascript 780 $renderer->doc .= ' 781 <div id="' . $mapid . '-table-span" class="olPOItableSpan"> 782 <table id="' . $mapid . '-table" class="olPOItable"> 783 <caption class="olPOITblCaption">' . $this->getLang('olmapPOItitle') . '</caption> 784 <thead class="olPOITblHeader"> 785 <tr> 786 <th class="rowId" scope="col">id</th> 787 <th class="icon" scope="col">' . $this->getLang('olmapPOIicon') . '</th> 788 <th class="lat" scope="col" title="' . $this->getLang('olmapPOIlatTitle') . '">' 789 . $this->getLang('olmapPOIlat') . '</th> 790 <th class="lon" scope="col" title="' . $this->getLang('olmapPOIlonTitle') . '">' 791 . $this->getLang('olmapPOIlon') . '</th> 792 <th class="txt" scope="col">' . $this->getLang('olmapPOItxt') . '</th> 793 </tr> 794 </thead>'; 795 if ($poitabledesc != '') { 796 $renderer->doc .= '<tfoot class="olPOITblFooter"><tr><td colspan="5">' . $poitabledesc 797 . '</td></tr></tfoot>'; 798 } 799 $renderer->doc .= '<tbody class="olPOITblBody">' . $poitable . '</tbody> 800 </table> 801 </div>'; 802 $renderer->doc .= "\n"; 803 } 804 // render inline mapscript parts 805 $renderer->doc .= '<script defer="defer" src="data:text/javascript;base64,'; 806 $renderer->doc .= base64_encode("olMapData[$mapnumber] = $param"); 807 $renderer->doc .= '"></script>'; 808 $mapnumber++; 809 return true; 810 } elseif ($format === 'metadata') { 811 if (!(($this->dflt ['lat'] == $mainLat) && ($this->dflt ['lon'] == $mainLon))) { 812 // render geo metadata, unless they are the default 813 $renderer->meta ['geo'] ['lat'] = $mainLat; 814 $renderer->meta ['geo'] ['lon'] = $mainLon; 815 if (($geophp = plugin_load('helper', 'geophp')) !== null) { 816 // if we have the geoPHP helper, add the geohash 817 try { 818 $renderer->meta['geo']['geohash'] = (new Point($mainLon, $mainLat))->out('geohash'); 819 } catch (Exception $e) { 820 Logger::error("Failed to create geohash for: $mainLat, $mainLon"); 821 } 822 } 823 } 824 825 if (($this->getConf('enableA11y')) && (!empty($_firstimage))) { 826 // add map local image into relation/firstimage if not already filled and when it is a local image 827 828 global $ID; 829 $rel = p_get_metadata($ID, 'relation', METADATA_RENDER_USING_CACHE); 830 // $img = $rel ['firstimage']; 831 if (empty($rel ['firstimage']) /* || $img == $_firstimage*/) { 832 //Logger::debug( 833 // 'olmap::render#rendering image relation metadata for _firstimage as $img was empty or same.', 834 // $_firstimage); 835 836 // This seems to never work; the firstimage entry in the .meta file is empty 837 // $renderer->meta['relation']['firstimage'] = $_firstimage; 838 // ... and neither does this; the firstimage entry in the .meta file is empty 839 // $relation = array('relation'=>array('firstimage'=>$_firstimage)); 840 // p_set_metadata($ID, $relation, false, false); 841 // ... this works 842 $renderer->internalmedia($_firstimage, $poitabledesc); 843 } 844 } 845 return true; 846 } 847 return false; 848 } 849} 850