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, strlen($img) - 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 // dbglog ( $result, 'syntax_plugin_openlayersmap_olmap::getStaticOSM: osm image url is:' ); 588 return $result; 589 } 590 591 /** 592 * Create a Bing maps static image url w/ the poi. 593 * 594 * @param array $gmap 595 * @param array $overlay 596 * @return string 597 */ 598 private function getBing(array $gmap, array $overlay): string { 599 switch($gmap ['baselyr']) { 600 case 've hybrid' : 601 case 'bing hybrid' : 602 $maptype = 'AerialWithLabels'; 603 break; 604 case 've sat' : 605 case 'bing sat' : 606 $maptype = 'Aerial'; 607 break; 608 case 've normal' : 609 case 've road' : 610 case 've' : 611 case 'bing road' : 612 default : 613 $maptype = 'Road'; 614 break; 615 } 616 $imgUrl = "https://dev.virtualearth.net/REST/v1/Imagery/Map/" . $maptype;// . "/"; 617 if($this->getConf('autoZoomMap')) { 618 $bbox = $this->calcBBOX($overlay, $gmap ['lat'], $gmap ['lon']); 619 //$imgUrl .= "?ma=" . $bbox ['minlat'] . "," . $bbox ['minlon'] . "," 620 // . $bbox ['maxlat'] . "," . $bbox ['maxlon']; 621 $imgUrl .= "?ma=" . $bbox ['minlat'] . "%2C" . $bbox ['minlon'] . "%2C" . $bbox ['maxlat'] 622 . "%2C" . $bbox ['maxlon']; 623 $imgUrl .= "&dcl=1"; 624 } 625 if(strpos($imgUrl, "?") === false) 626 $imgUrl .= "?"; 627 628 //$imgUrl .= "&ms=" . str_replace ( "px", "", $gmap ['width'] ) . "," 629 // . str_replace ( "px", "", $gmap ['height'] ); 630 $imgUrl .= "&ms=" . str_replace("px", "", $gmap ['width']) . "%2C" 631 . str_replace("px", "", $gmap ['height']); 632 $imgUrl .= "&key=" . $this->getConf('bingAPIKey'); 633 if(!empty ($overlay)) { 634 $rowId = 0; 635 foreach($overlay as $data) { 636 list ($lat, $lon, $text, $angle, $opacity, $img) = $data; 637 // TODO icon style lookup, see: http://msdn.microsoft.com/en-us/library/ff701719.aspx for iconStyle 638 $iconStyle = 32; 639 $rowId++; 640 // NOTE: the max number of pushpins is 18! or we have to use POST 641 // (http://msdn.microsoft.com/en-us/library/ff701724.aspx) 642 if($rowId == 18) { 643 break; 644 } 645 //$imgUrl .= "&pp=$lat,$lon;$iconStyle;$rowId"; 646 $imgUrl .= "&pp=$lat%2C$lon%3B$iconStyle%3B$rowId"; 647 648 } 649 } 650 global $conf; 651 $imgUrl .= "&fmt=png"; 652 $imgUrl .= "&c=" . $conf ['lang']; 653 // dbglog($imgUrl,'syntax_plugin_openlayersmap_olmap::getBing: bing image url is:'); 654 return $imgUrl; 655 } 656 657 /** 658 * Calculate the minimum bbox for a start location + poi. 659 * 660 * @param array $overlay 661 * multi-dimensional array of array($lat, $lon, $text, $angle, $opacity, $img) 662 * @param float $lat 663 * latitude for map center 664 * @param float $lon 665 * longitude for map center 666 * @return array :float array describing the mbr and center point 667 */ 668 private function calcBBOX(array $overlay, float $lat, float $lon): array { 669 $lats = array($lat); 670 $lons = array($lon); 671 foreach($overlay as $data) { 672 list ($lat, $lon, $text, $angle, $opacity, $img) = $data; 673 $lats [] = $lat; 674 $lons [] = $lon; 675 } 676 sort($lats); 677 sort($lons); 678 // TODO: make edge/wrap around cases work 679 $centerlat = $lats [0] + ($lats [count($lats) - 1] - $lats [0]); 680 $centerlon = $lons [0] + ($lons [count($lats) - 1] - $lons [0]); 681 return array( 682 'minlat' => $lats [0], 683 'minlon' => $lons [0], 684 'maxlat' => $lats [count($lats) - 1], 685 'maxlon' => $lons [count($lats) - 1], 686 'centerlat' => $centerlat, 687 'centerlon' => $centerlon 688 ); 689 } 690 691 /** 692 * convert latitude in decimal degrees to DMS+hemisphere. 693 * 694 * @param float $decimaldegrees 695 * @return string 696 * @todo move this into a shared library 697 */ 698 private function convertLat(float $decimaldegrees): string { 699 if(strpos($decimaldegrees, '-') !== false) { 700 $latPos = "S"; 701 } else { 702 $latPos = "N"; 703 } 704 $dms = $this->convertDDtoDMS(abs($decimaldegrees)); 705 return hsc($dms . $latPos); 706 } 707 708 /** 709 * Convert decimal degrees to degrees, minutes, seconds format 710 * 711 * @param float $decimaldegrees 712 * @return string dms 713 * @todo move this into a shared library 714 */ 715 private function convertDDtoDMS(float $decimaldegrees): string { 716 $dms = floor($decimaldegrees); 717 $secs = ($decimaldegrees - $dms) * 3600; 718 $min = floor($secs / 60); 719 $sec = round($secs - ($min * 60), 3); 720 $dms .= 'º' . $min . '\'' . $sec . '"'; 721 return $dms; 722 } 723 724 /** 725 * convert longitude in decimal degrees to DMS+hemisphere. 726 * 727 * @param float $decimaldegrees 728 * @return string 729 * @todo move this into a shared library 730 */ 731 private function convertLon(float $decimaldegrees): string { 732 if(strpos($decimaldegrees, '-') !== false) { 733 $lonPos = "W"; 734 } else { 735 $lonPos = "E"; 736 } 737 $dms = $this->convertDDtoDMS(abs($decimaldegrees)); 738 return hsc($dms . $lonPos); 739 } 740 741 /** 742 * Figures out the base filename of a media path. 743 * 744 * @param string $mediaLink 745 * @return string 746 */ 747 private function getFileName(string $mediaLink): string { 748 $mediaLink = str_replace('[[', '', $mediaLink); 749 $mediaLink = str_replace(']]', '', $mediaLink); 750 $mediaLink = substr($mediaLink, 0, -4); 751 $parts = explode(':', $mediaLink); 752 $mediaLink = end($parts); 753 return str_replace('_', ' ', $mediaLink); 754 } 755 756 /** 757 * 758 * @see DokuWiki_Syntax_Plugin::render() 759 */ 760 public function render($format, Doku_Renderer $renderer, $data): bool { 761 // set to true after external scripts tags are written 762 static $initialised = false; 763 // incremented for each map tag in the page source so we can keep track of each map in this page 764 static $mapnumber = 0; 765 766 // dbglog($data, 'olmap::render() data.'); 767 list ($mapid, $param, $mainLat, $mainLon, $poitable, $poitabledesc, $staticImgUrl, $_firstimage) = $data; 768 769 if($format == 'xhtml') { 770 $olscript = ''; 771 $stamenEnable = $this->getConf('enableStamen'); 772 $osmEnable = $this->getConf('enableOSM'); 773 $enableBing = $this->getConf('enableBing'); 774 775 $scriptEnable = ''; 776 if(!$initialised) { 777 $initialised = true; 778 // render necessary script tags only once 779 $olscript = '<script defer="defer" src="' . DOKU_BASE . 'lib/plugins/openlayersmap/ol7/ol.js"></script> 780<script defer="defer" src="' . DOKU_BASE . 'lib/plugins/openlayersmap/ol7/ol-layerswitcher.js"></script>'; 781 782 $scriptEnable = '<script defer="defer" src="data:text/javascript;base64,'; 783 $scriptSrc = $olscript ? 'const olEnable=true;' : 'const olEnable=false;'; 784 $scriptSrc .= 'const osmEnable=' . ($osmEnable ? 'true' : 'false') . ';'; 785 $scriptSrc .= 'const stamenEnable=' . ($stamenEnable ? 'true' : 'false') . ';'; 786 $scriptSrc .= 'const bEnable=' . ($enableBing ? 'true' : 'false') . ';'; 787 $scriptSrc .= 'const bApiKey="' . $this->getConf('bingAPIKey') . '";'; 788 $scriptSrc .= 'const tfApiKey="' . $this->getConf('tfApiKey') . '";'; 789 $scriptSrc .= 'const gApiKey="' . $this->getConf('googleAPIkey') . '";'; 790 $scriptSrc .= 'olMapData = []; let olMaps = {}; let olMapOverlays = {};'; 791 $scriptEnable .= base64_encode($scriptSrc); 792 $scriptEnable .= '"></script>'; 793 } 794 $renderer->doc .= "$olscript\n$scriptEnable"; 795 $renderer->doc .= '<div class="olMapHelp">' . $this->locale_xhtml("help") . '</div>'; 796 if($this->getConf('enableA11y')) { 797 $renderer->doc .= '<div id="' . $mapid . '-static" class="olStaticMap">' 798 . p_render($format, p_get_instructions($staticImgUrl), $info) . '</div>'; 799 } 800 $renderer->doc .= '<div id="' . $mapid . '-clearer" class="clearer"><p> </p></div>'; 801 if($this->getConf('enableA11y')) { 802 // render a table of the POI for the print and a11y presentation, it is hidden using javascript 803 $renderer->doc .= ' 804 <div id="' . $mapid . '-table-span" class="olPOItableSpan"> 805 <table id="' . $mapid . '-table" class="olPOItable"> 806 <caption class="olPOITblCaption">' . $this->getLang('olmapPOItitle') . '</caption> 807 <thead class="olPOITblHeader"> 808 <tr> 809 <th class="rowId" scope="col">id</th> 810 <th class="icon" scope="col">' . $this->getLang('olmapPOIicon') . '</th> 811 <th class="lat" scope="col" title="' . $this->getLang('olmapPOIlatTitle') . '">' 812 . $this->getLang('olmapPOIlat') . '</th> 813 <th class="lon" scope="col" title="' . $this->getLang('olmapPOIlonTitle') . '">' 814 . $this->getLang('olmapPOIlon') . '</th> 815 <th class="txt" scope="col">' . $this->getLang('olmapPOItxt') . '</th> 816 </tr> 817 </thead>'; 818 if($poitabledesc != '') { 819 $renderer->doc .= '<tfoot class="olPOITblFooter"><tr><td colspan="5">' . $poitabledesc 820 . '</td></tr></tfoot>'; 821 } 822 $renderer->doc .= '<tbody class="olPOITblBody">' . $poitable . '</tbody> 823 </table> 824 </div>'; 825 $renderer->doc .= "\n"; 826 } 827 // render inline mapscript parts 828 $renderer->doc .= '<script defer="defer" src="data:text/javascript;base64,'; 829 $renderer->doc .= base64_encode("olMapData[$mapnumber] = $param"); 830 $renderer->doc .= '"></script>'; 831 $mapnumber++; 832 return true; 833 } elseif($format == 'metadata') { 834 if(!(($this->dflt ['lat'] == $mainLat) && ($this->dflt ['lon'] == $mainLon))) { 835 // render geo metadata, unless they are the default 836 $renderer->meta ['geo'] ['lat'] = $mainLat; 837 $renderer->meta ['geo'] ['lon'] = $mainLon; 838 if($geophp = plugin_load('helper', 'geophp')) { 839 // if we have the geoPHP helper, add the geohash 840 841 // fails with older php versions.. 842 // $renderer->meta['geo']['geohash'] = (new Point($mainLon,$mainLat))->out('geohash'); 843 $p = new Point ($mainLon, $mainLat); 844 $renderer->meta ['geo'] ['geohash'] = $p->out('geohash'); 845 } 846 } 847 848 if(($this->getConf('enableA11y')) && (!empty ($_firstimage))) { 849 // add map local image into relation/firstimage if not already filled and when it is a local image 850 851 global $ID; 852 $rel = p_get_metadata($ID, 'relation', METADATA_RENDER_USING_CACHE); 853 // $img = $rel ['firstimage']; 854 if(empty ($rel ['firstimage']) /* || $img == $_firstimage*/) { 855 //dbglog ( $_firstimage, 856 // 'olmap::render#rendering image relation metadata for _firstimage as $img was empty or same.' ); 857 // This seems to never work; the firstimage entry in the .meta file is empty 858 // $renderer->meta['relation']['firstimage'] = $_firstimage; 859 860 // ... and neither does this; the firstimage entry in the .meta file is empty 861 // $relation = array('relation'=>array('firstimage'=>$_firstimage)); 862 // p_set_metadata($ID, $relation, false, false); 863 864 // ... this works 865 $renderer->internalmedia($_firstimage, $poitabledesc); 866 } 867 } 868 return true; 869 } 870 return false; 871 } 872} 873