1<?php 2 3/* 4 * Copyright (c) 2008-2021 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; 22 23/** 24 * DokuWiki Plugin openlayersmap (Syntax Component). 25 * Provides for display of an OpenLayers based map in a wiki page. 26 * 27 * @author Mark Prins 28 */ 29class syntax_plugin_openlayersmap_olmap extends SyntaxPlugin 30{ 31 /** 32 * defaults of the known attributes of the olmap tag. 33 */ 34 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' => '']; 35 36 /** 37 * 38 * @see DokuWiki_Syntax_Plugin::getType() 39 */ 40 public function getType(): string 41 { 42 return 'substition'; 43 } 44 45 /** 46 * 47 * @see DokuWiki_Syntax_Plugin::getPType() 48 */ 49 public function getPType(): string 50 { 51 return 'block'; 52 } 53 54 /** 55 * 56 * @see Doku_Parser_Mode::getSort() 57 */ 58 public function getSort(): int 59 { 60 return 901; 61 } 62 63 /** 64 * 65 * @see Doku_Parser_Mode::connectTo() 66 */ 67 public function connectTo($mode) 68 { 69 $this->Lexer->addSpecialPattern( 70 '<olmap ?[^>\n]*>.*?</olmap>', 71 $mode, 72 'plugin_openlayersmap_olmap' 73 ); 74 } 75 76 /** 77 * 78 * @see DokuWiki_Syntax_Plugin::handle() 79 */ 80 public function handle($match, $state, $pos, Doku_Handler $handler): array 81 { 82 // break matched data into its components 83 $_tag = explode('>', substr($match, 7, -9), 2); 84 $str_params = $_tag[0]; 85 if (array_key_exists(1, $_tag)) { 86 $str_points = $_tag[1]; 87 } else { 88 $str_points = ''; 89 } 90 // get the lat/lon for adding them to the metadata (used by geotag) 91 preg_match('(lat[:|=]\"-?\d*\.?\d*\")', $match, $mainLat); 92 preg_match('(lon[:|=]\"-?\d*\.?\d*\")', $match, $mainLon); 93 $mainLat = substr($mainLat [0], 5, -1); 94 $mainLon = substr($mainLon [0], 5, -1); 95 if (!is_numeric($mainLat)) { 96 $mainLat = $this->dflt ['lat']; 97 } 98 if (!is_numeric($mainLon)) { 99 $mainLon = $this->dflt ['lon']; 100 } 101 102 $gmap = $this->extractParams($str_params); 103 $overlay = $this->extractPoints($str_points); 104 $_firstimageID = ''; 105 106 $_nocache = false; 107 // choose maptype based on the specified tag 108 $imgUrl = "{{"; 109 if (stripos($gmap ['baselyr'], 'google') !== false) { 110 // Google 111 $imgUrl .= $this->getGoogle($gmap, $overlay); 112 $imgUrl .= "&.png"; 113 } elseif (stripos($gmap ['baselyr'], 'bing') !== false) { 114 // Bing 115 if (!$this->getConf('bingAPIKey')) { 116 // in case there is no Bing api key we'll use OSM 117 $_firstimageID = $this->getStaticOSM($gmap, $overlay); 118 $imgUrl .= $_firstimageID; 119 if ($this->getConf('optionStaticMapGenerator') == 'remote') { 120 $imgUrl .= "&.png"; 121 } 122 } else { 123 // seems that Bing doesn't like the DW client, turn off caching 124 $_nocache = true; 125 $imgUrl .= $this->getBing($gmap, $overlay) . "&.png"; 126 } 127 } /* elseif (stripos ( $gmap ['baselyr'], 'mapquest' ) !== false) { 128 // MapQuest 129 if (! $this->getConf ( 'mapquestAPIKey' )) { 130 // no API key for MapQuest, use OSM 131 $_firstimageID = $this->getStaticOSM ( $gmap, $overlay ); 132 $imgUrl .= $_firstimageID; 133 if ($this->getConf ( 'optionStaticMapGenerator' ) == 'remote') { 134 $imgUrl .= "&.png"; 135 } 136 } else { 137 $imgUrl .= $this->_getMapQuest ( $gmap, $overlay ); 138 $imgUrl .= "&.png"; 139 } 140 } */ else { 141 // default OSM 142 $_firstimageID = $this->getStaticOSM($gmap, $overlay); 143 $imgUrl .= $_firstimageID; 144 if ($this->getConf('optionStaticMapGenerator') == 'remote') { 145 $imgUrl .= "&.png"; 146 } 147} 148 149 // append dw p_render specific params and render 150 $imgUrl .= "?" . str_replace("px", "", $gmap ['width']) . "x" 151 . str_replace("px", "", $gmap ['height']); 152 $imgUrl .= "&nolink"; 153 154 // add nocache option for selected services 155if ($_nocache) { 156 $imgUrl .= "&nocache"; 157} 158 159 $imgUrl .= " |" . $gmap ['summary'] . " }}"; 160 161 // dbglog($imgUrl,"complete image tags is:"); 162 163 $mapid = $gmap ['id']; 164 // create a javascript parameter string for the map 165 $param = ''; 166foreach ($gmap as $key => $val) { 167 $param .= is_numeric($val) ? "$key: $val, " : "$key: '" . hsc($val) . "', "; 168} 169if (!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; 178if ($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} 204if (!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} 214if (!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} 225if (!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 // dbglog($imgUrl,'syntax_plugin_openlayersmap_olmap::getGoogle: Google image url is:'); 387 return $imgUrl; 388 } 389 390 /** 391 * Create a MapQuest static map API image url. 392 * 393 * @param array $gmap 394 * @param array $overlay 395 */ 396 /* 397 private function _getMapQuest($gmap, $overlay) { 398 $sUrl = $this->getConf ( 'iconUrlOverload' ); 399 if (! $sUrl) { 400 $sUrl = DOKU_URL; 401 } 402 switch ($gmap ['baselyr']) { 403 case 'mapquest hybrid' : 404 $maptype = 'hyb'; 405 break; 406 case 'mapquest sat' : 407 // because sat coverage is very limited use 'hyb' instead of 'sat' so we don't get a blank map 408 $maptype = 'hyb'; 409 break; 410 case 'mapquest road' : 411 default : 412 $maptype = 'map'; 413 break; 414 } 415 $imgUrl = "http://open.mapquestapi.com/staticmap/v4/getmap?declutter=true&"; 416 if (count ( $overlay ) < 1) { 417 $imgUrl .= "?center=" . $gmap ['lat'] . "," . $gmap ['lon']; 418 // max level for mapquest is 16 419 if ($gmap ['zoom'] > 16) { 420 $imgUrl .= "&zoom=16"; 421 } else { 422 $imgUrl .= "&zoom=" . $gmap ['zoom']; 423 } 424 } 425 // use bestfit instead of center/zoom, needs upperleft/lowerright corners 426 // $bbox=$this->calcBBOX($overlay, $gmap['lat'], $gmap['lon']); 427 // $imgUrl .= "bestfit=".$bbox['minlat'].",".$bbox['maxlon'].",".$bbox['maxlat'].",".$bbox['minlon']; 428 429 // TODO declutter option works well for square maps but not for rectangular, maybe compensate for that 430 // or compensate the mbr.. 431 432 $imgUrl .= "&size=" . str_replace ( "px", "", $gmap ['width'] ) . "," . str_replace ("px","",$gmap['height']); 433 434 // TODO mapquest allows using one image url with a multiplier $NUMBER eg: 435 // $NUMBER = 2 436 // $imgUrl .= DOKU_URL."/".DOKU_PLUGIN."/".getPluginName()."/icons/".$img.",$NUMBER,C," 437 // .$lat1.",".$lon1.",0,0,0,0,C,".$lat2.",".$lon2.",0,0,0,0"; 438 if (! empty ( $overlay )) { 439 $imgUrl .= "&xis="; 440 foreach ( $overlay as $data ) { 441 list ( $lat, $lon, $text, $angle, $opacity, $img ) = $data; 442 // $imgUrl .= $sUrl."lib/plugins/openlayersmap/icons/".$img.",1,C,".$lat.",".$lon.",0,0,0,0,"; 443 $imgUrl .= $sUrl . "lib/plugins/openlayersmap/icons/" . $img . ",1,C," . $lat . "," . $lon . ","; 444 } 445 $imgUrl = substr ( $imgUrl, 0, - 1 ); 446 } 447 $imgUrl .= "&imageType=png&type=" . $maptype; 448 $imgUrl .= "&key=".$this->getConf ( 'mapquestAPIKey' ); 449 // dbglog($imgUrl,'syntax_plugin_openlayersmap_olmap::_getMapQuest: MapQuest image url is:'); 450 return $imgUrl; 451 } 452 */ 453 454 /** 455 * Create a static OSM map image url w/ the poi from http://staticmap.openstreetmap.de (staticMapLite) 456 * use http://staticmap.openstreetmap.de "staticMapLite" or a local version 457 * 458 * @param array $gmap 459 * @param array $overlay 460 * 461 * @return false|string 462 * @todo implementation for http://ojw.dev.openstreetmap.org/StaticMapDev/ 463 */ 464 private function getStaticOSM(array $gmap, array $overlay) 465 { 466 global $conf; 467 468 if ($this->getConf('optionStaticMapGenerator') === 'local') { 469 // using local basemap composer 470 if (($myMap = plugin_load('helper', 'openlayersmap_staticmap')) === null) { 471 dbglog( 472 $myMap, 473 'openlayersmap_staticmap plugin is not available for use.' 474 ); 475 } 476 if (($geophp = plugin_load('helper', 'geophp')) === null) { 477 dbglog($geophp, 'geophp plugin is not available for use.'); 478 } 479 $size = str_replace("px", "", $gmap ['width']) . "x" 480 . str_replace("px", "", $gmap ['height']); 481 482 $markers = []; 483 if ($overlay !== []) { 484 foreach ($overlay as $data) { 485 [$lat, $lon, $text, $angle, $opacity, $img] = $data; 486 $iconStyle = substr($img, 0, -4); 487 $markers [] = ['lat' => $lat, 'lon' => $lon, 'type' => $iconStyle]; 488 } 489 } 490 491 $apikey = ''; 492 switch ($gmap ['baselyr']) { 493 case 'mapnik': 494 case 'openstreetmap': 495 $maptype = 'openstreetmap'; 496 break; 497 case 'transport': 498 $maptype = 'transport'; 499 $apikey = '?apikey=' . $this->getConf('tfApiKey'); 500 break; 501 case 'landscape': 502 $maptype = 'landscape'; 503 $apikey = '?apikey=' . $this->getConf('tfApiKey'); 504 break; 505 case 'outdoors': 506 $maptype = 'outdoors'; 507 $apikey = '?apikey=' . $this->getConf('tfApiKey'); 508 break; 509 case 'cycle map': 510 $maptype = 'cycle'; 511 $apikey = '?apikey=' . $this->getConf('tfApiKey'); 512 break; 513 case 'hike and bike map': 514 $maptype = 'hikeandbike'; 515 break; 516 case 'mapquest hybrid': 517 case 'mapquest road': 518 case 'mapquest sat': 519 $maptype = 'mapquest'; 520 break; 521 default: 522 $maptype = ''; 523 break; 524 } 525 526 $result = $myMap->getMap( 527 $gmap ['lat'], 528 $gmap ['lon'], 529 $gmap ['zoom'], 530 $size, 531 $maptype, 532 $markers, 533 $gmap ['gpxfile'], 534 $gmap ['kmlfile'], 535 $gmap ['geojsonfile'], 536 $apikey 537 ); 538 } else { 539 // using external basemap composer 540 541 // https://staticmap.openstreetmap.de/staticmap.php?center=47.000622235634,10 542 //.117187497601&zoom=5&size=500x350 543 // &markers=48.999812532766,8.3593749976708,lightblue1|43.154850037315,17.499999997306, 544 // lightblue1|49.487527053077,10.820312497573,ltblu-pushpin|47.951071133739,15.917968747369, 545 // ol-marker|47.921629720114,18.027343747285,ol-marker-gold|47.951071133739,19.257812497236, 546 // ol-marker-blue|47.180141361692,19.257812497236,ol-marker-green 547 $imgUrl = "https://staticmap.openstreetmap.de/staticmap.php"; 548 $imgUrl .= "?center=" . $gmap ['lat'] . "," . $gmap ['lon']; 549 $imgUrl .= "&size=" . str_replace("px", "", $gmap ['width']) . "x" 550 . str_replace("px", "", $gmap ['height']); 551 552 if ($gmap ['zoom'] > 16) { 553 // actually this could even be 18, but that seems overkill 554 $imgUrl .= "&zoom=16"; 555 } else { 556 $imgUrl .= "&zoom=" . $gmap ['zoom']; 557 } 558 559 if ($overlay !== []) { 560 $rowId = 0; 561 $imgUrl .= "&markers="; 562 foreach ($overlay as $data) { 563 [$lat, $lon, $text, $angle, $opacity, $img] = $data; 564 $rowId++; 565 $iconStyle = "lightblue$rowId"; 566 $imgUrl .= "$lat,$lon,$iconStyle%7c"; 567 } 568 $imgUrl = substr($imgUrl, 0, -3); 569 } 570 571 $result = $imgUrl; 572 } 573 return $result; 574 } 575 576 /** 577 * Create a Bing maps static image url w/ the poi. 578 * 579 * @param array $gmap 580 * @param array $overlay 581 */ 582 private function getBing(array $gmap, array $overlay): string 583 { 584 switch ($gmap ['baselyr']) { 585 case 've hybrid': 586 case 'bing hybrid': 587 $maptype = 'AerialWithLabels'; 588 break; 589 case 've sat': 590 case 'bing sat': 591 $maptype = 'Aerial'; 592 break; 593 case 've normal': 594 case 've road': 595 case 've': 596 case 'bing road': 597 default: 598 $maptype = 'Road'; 599 break; 600 } 601 $imgUrl = "https://dev.virtualearth.net/REST/v1/Imagery/Map/" . $maptype;// . "/"; 602 if ($this->getConf('autoZoomMap')) { 603 $bbox = $this->calcBBOX($overlay, $gmap ['lat'], $gmap ['lon']); 604 //$imgUrl .= "?ma=" . $bbox ['minlat'] . "," . $bbox ['minlon'] . "," 605 // . $bbox ['maxlat'] . "," . $bbox ['maxlon']; 606 $imgUrl .= "?ma=" . $bbox ['minlat'] . "%2C" . $bbox ['minlon'] . "%2C" . $bbox ['maxlat'] 607 . "%2C" . $bbox ['maxlon']; 608 $imgUrl .= "&dcl=1"; 609 } 610 if (strpos($imgUrl, "?") === false) 611 $imgUrl .= "?"; 612 613 //$imgUrl .= "&ms=" . str_replace ( "px", "", $gmap ['width'] ) . "," 614 // . str_replace ( "px", "", $gmap ['height'] ); 615 $imgUrl .= "&ms=" . str_replace("px", "", $gmap ['width']) . "%2C" 616 . str_replace("px", "", $gmap ['height']); 617 $imgUrl .= "&key=" . $this->getConf('bingAPIKey'); 618 if ($overlay !== []) { 619 $rowId = 0; 620 foreach ($overlay as $data) { 621 [$lat, $lon, $text, $angle, $opacity, $img] = $data; 622 // TODO icon style lookup, see: http://msdn.microsoft.com/en-us/library/ff701719.aspx for iconStyle 623 $iconStyle = 32; 624 $rowId++; 625 // NOTE: the max number of pushpins is 18! or we have to use POST 626 // (http://msdn.microsoft.com/en-us/library/ff701724.aspx) 627 if ($rowId == 18) { 628 break; 629 } 630 //$imgUrl .= "&pp=$lat,$lon;$iconStyle;$rowId"; 631 $imgUrl .= "&pp=$lat%2C$lon%3B$iconStyle%3B$rowId"; 632 } 633 } 634 global $conf; 635 $imgUrl .= "&fmt=png"; 636 $imgUrl .= "&c=" . $conf ['lang']; 637 return $imgUrl; 638 } 639 640 /** 641 * Calculate the minimum bbox for a start location + poi. 642 * 643 * @param array $overlay 644 * multi-dimensional array of array($lat, $lon, $text, $angle, $opacity, $img) 645 * @param float $lat 646 * latitude for map center 647 * @param float $lon 648 * longitude for map center 649 * @return array :float array describing the mbr and center point 650 */ 651 private function calcBBOX(array $overlay, float $lat, float $lon): array 652 { 653 $lats = [$lat]; 654 $lons = [$lon]; 655 foreach ($overlay as $data) { 656 [$lat, $lon, $text, $angle, $opacity, $img] = $data; 657 $lats [] = $lat; 658 $lons [] = $lon; 659 } 660 sort($lats); 661 sort($lons); 662 // TODO: make edge/wrap around cases work 663 $centerlat = $lats [0] + ($lats [count($lats) - 1] - $lats [0]); 664 $centerlon = $lons [0] + ($lons [count($lats) - 1] - $lons [0]); 665 return ['minlat' => $lats [0], 'minlon' => $lons [0], 'maxlat' => $lats [count($lats) - 1], 'maxlon' => $lons [count($lats) - 1], 'centerlat' => $centerlat, 'centerlon' => $centerlon]; 666 } 667 668 /** 669 * convert latitude in decimal degrees to DMS+hemisphere. 670 * 671 * @param float $decimaldegrees 672 * @todo move this into a shared library 673 */ 674 private function convertLat(float $decimaldegrees): string 675 { 676 if (strpos($decimaldegrees, '-') !== false) { 677 $latPos = "S"; 678 } else { 679 $latPos = "N"; 680 } 681 $dms = $this->convertDDtoDMS(abs($decimaldegrees)); 682 return hsc($dms . $latPos); 683 } 684 685 /** 686 * Convert decimal degrees to degrees, minutes, seconds format 687 * 688 * @param float $decimaldegrees 689 * @return string dms 690 * @todo move this into a shared library 691 */ 692 private function convertDDtoDMS(float $decimaldegrees): string 693 { 694 $dms = floor($decimaldegrees); 695 $secs = ($decimaldegrees - $dms) * 3600; 696 $min = floor($secs / 60); 697 $sec = round($secs - ($min * 60), 3); 698 $dms .= 'º' . $min . '\'' . $sec . '"'; 699 return $dms; 700 } 701 702 /** 703 * convert longitude in decimal degrees to DMS+hemisphere. 704 * 705 * @param float $decimaldegrees 706 * @todo move this into a shared library 707 */ 708 private function convertLon(float $decimaldegrees): string 709 { 710 if (strpos($decimaldegrees, '-') !== false) { 711 $lonPos = "W"; 712 } else { 713 $lonPos = "E"; 714 } 715 $dms = $this->convertDDtoDMS(abs($decimaldegrees)); 716 return hsc($dms . $lonPos); 717 } 718 719 /** 720 * Figures out the base filename of a media path. 721 * 722 * @param string $mediaLink 723 */ 724 private function getFileName(string $mediaLink): string 725 { 726 $mediaLink = str_replace('[[', '', $mediaLink); 727 $mediaLink = str_replace(']]', '', $mediaLink); 728 $mediaLink = substr($mediaLink, 0, -4); 729 730 $parts = explode(':', $mediaLink); 731 $mediaLink = end($parts); 732 return str_replace('_', ' ', $mediaLink); 733 } 734 735 /** 736 * 737 * @see DokuWiki_Syntax_Plugin::render() 738 */ 739 public function render($format, Doku_Renderer $renderer, $data): bool 740 { 741 // set to true after external scripts tags are written 742 static $initialised = false; 743 // incremented for each map tag in the page source so we can keep track of each map in this page 744 static $mapnumber = 0; 745 746 [$mapid, $param, $mainLat, $mainLon, $poitable, $poitabledesc, $staticImgUrl, $_firstimage] = $data; 747 748 if ($format === 'xhtml') { 749 $olscript = ''; 750 $stadiaEnable = $this->getConf('enableStadia'); 751 $osmEnable = $this->getConf('enableOSM'); 752 $enableBing = $this->getConf('enableBing'); 753 754 $scriptEnable = ''; 755 if (!$initialised) { 756 $initialised = true; 757 // render necessary script tags only once 758 $olscript = '<script defer="defer" src="' . DOKU_BASE . 'lib/plugins/openlayersmap/ol/ol.js"></script> 759<script defer="defer" src="' . DOKU_BASE . 'lib/plugins/openlayersmap/ol/ol-layerswitcher.js"></script>'; 760 761 $scriptEnable = '<script defer="defer" src="data:text/javascript;base64,'; 762 $scriptSrc = $olscript ? 'const olEnable=true;' : 'const olEnable=false;'; 763 $scriptSrc .= 'const osmEnable=' . ($osmEnable ? 'true' : 'false') . ';'; 764 $scriptSrc .= 'const stadiaEnable=' . ($stadiaEnable ? 'true' : 'false') . ';'; 765 $scriptSrc .= 'const bEnable=' . ($enableBing ? 'true' : 'false') . ';'; 766 $scriptSrc .= 'const bApiKey="' . $this->getConf('bingAPIKey') . '";'; 767 $scriptSrc .= 'const tfApiKey="' . $this->getConf('tfApiKey') . '";'; 768 $scriptSrc .= 'const gApiKey="' . $this->getConf('googleAPIkey') . '";'; 769 $scriptSrc .= 'olMapData = []; let olMaps = {}; let olMapOverlays = {};'; 770 $scriptEnable .= base64_encode($scriptSrc); 771 $scriptEnable .= '"></script>'; 772 } 773 $renderer->doc .= "$olscript\n$scriptEnable"; 774 $renderer->doc .= '<div class="olMapHelp">' . $this->locale_xhtml("help") . '</div>'; 775 if ($this->getConf('enableA11y')) { 776 $renderer->doc .= '<div id="' . $mapid . '-static" class="olStaticMap">' 777 . p_render($format, p_get_instructions($staticImgUrl), $info) . '</div>'; 778 } 779 $renderer->doc .= '<div id="' . $mapid . '-clearer" class="clearer"><p> </p></div>'; 780 if ($this->getConf('enableA11y')) { 781 // render a table of the POI for the print and a11y presentation, it is hidden using javascript 782 $renderer->doc .= ' 783 <div id="' . $mapid . '-table-span" class="olPOItableSpan"> 784 <table id="' . $mapid . '-table" class="olPOItable"> 785 <caption class="olPOITblCaption">' . $this->getLang('olmapPOItitle') . '</caption> 786 <thead class="olPOITblHeader"> 787 <tr> 788 <th class="rowId" scope="col">id</th> 789 <th class="icon" scope="col">' . $this->getLang('olmapPOIicon') . '</th> 790 <th class="lat" scope="col" title="' . $this->getLang('olmapPOIlatTitle') . '">' 791 . $this->getLang('olmapPOIlat') . '</th> 792 <th class="lon" scope="col" title="' . $this->getLang('olmapPOIlonTitle') . '">' 793 . $this->getLang('olmapPOIlon') . '</th> 794 <th class="txt" scope="col">' . $this->getLang('olmapPOItxt') . '</th> 795 </tr> 796 </thead>'; 797 if ($poitabledesc != '') { 798 $renderer->doc .= '<tfoot class="olPOITblFooter"><tr><td colspan="5">' . $poitabledesc 799 . '</td></tr></tfoot>'; 800 } 801 $renderer->doc .= '<tbody class="olPOITblBody">' . $poitable . '</tbody> 802 </table> 803 </div>'; 804 $renderer->doc .= "\n"; 805 } 806 // render inline mapscript parts 807 $renderer->doc .= '<script defer="defer" src="data:text/javascript;base64,'; 808 $renderer->doc .= base64_encode("olMapData[$mapnumber] = $param"); 809 $renderer->doc .= '"></script>'; 810 $mapnumber++; 811 return true; 812 } elseif ($format === 'metadata') { 813 if (!(($this->dflt ['lat'] == $mainLat) && ($this->dflt ['lon'] == $mainLon))) { 814 // render geo metadata, unless they are the default 815 $renderer->meta ['geo'] ['lat'] = $mainLat; 816 $renderer->meta ['geo'] ['lon'] = $mainLon; 817 if (($geophp = plugin_load('helper', 'geophp')) !== null) { 818 // if we have the geoPHP helper, add the geohash 819 820 // fails with older php versions.. 821 // $renderer->meta['geo']['geohash'] = (new Point($mainLon,$mainLat))->out('geohash'); 822 $p = new Point($mainLon, $mainLat); 823 $renderer->meta ['geo'] ['geohash'] = $p->out('geohash'); 824 } 825 } 826 827 if (($this->getConf('enableA11y')) && (!empty($_firstimage))) { 828 // add map local image into relation/firstimage if not already filled and when it is a local image 829 830 global $ID; 831 $rel = p_get_metadata($ID, 'relation', METADATA_RENDER_USING_CACHE); 832 // $img = $rel ['firstimage']; 833 if (empty($rel ['firstimage']) /* || $img == $_firstimage*/) { 834 //dbglog ( $_firstimage, 835 // 'olmap::render#rendering image relation metadata for _firstimage as $img was empty or same.' ); 836 // This seems to never work; the firstimage entry in the .meta file is empty 837 // $renderer->meta['relation']['firstimage'] = $_firstimage; 838 839 // ... and neither does this; the firstimage entry in the .meta file is empty 840 // $relation = array('relation'=>array('firstimage'=>$_firstimage)); 841 // p_set_metadata($ID, $relation, false, false); 842 843 // ... this works 844 $renderer->internalmedia($_firstimage, $poitabledesc); 845 } 846 } 847 return true; 848 } 849 return false; 850 } 851} 852