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