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