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