, implement caption
# 2021-06-18 Fix incorrect placement of "-" in regex for PHP 7.4
# 2021-04-21 Caption, inline-block
# 2018-07-10 Don't generate zoom parameter when llbs or nzbs
# 2018-05-14 Call topomap with https
# 2018-05-12 PHP 7.2
# 2015-07-01 Attachments corrected (gpx, kml requires extra processing)
# 2014-08-09 Markup_e PHP 5.5 compatible, add mapref parameter
# 2013-07-15 Initial version
*/
$NewVersion = ''; # empty string for production version, .new. for non-prod
$NZTopoName = 'NZTopo';
$NZTopoMUName = $NZTopoName . $NewVersion;
# Version date
$RecipeInfo[$NZTopoName]['Version'] = '2022-01-07' . $NewVersion;
$FmtPV['$NZTopoVersion'] = "'$NZTopoName version {$RecipeInfo[$NZTopoName]['Version']}'"; // return version as a custom page variable
SDV($NZTopoZoom, 13); # set default zoom factor
SDV($NZTopoNewwin, 1); # set default new window setting
SDV($NZTopoDebug, false); # set default debug setting
SDV($HTMLStylesFmt['NZTopo'], # set default styles
'.nztopo figcaption {font-size:smaller;}');
# declare $NZTopo for (:if enabled NZTopo:) recipe installation check
$NZTopo = 1; # enabled
# set debug flag
$debugon = (bool) $NZTopoDebug; # if on writes input and output to web page
// Debug function
function sms(string $smstext, $switch=0){ # see https://www.pmwiki.org/wiki/Cookbook/DebuggingForCookbookAuthors
global $MessagesFmt;
if ($switch == true || is_array($text)) {
$MessagesFmt[$NZTopoName] .= "
" . print_r($text,true) . "
\n";
} else {
$MessagesFmt[$NZTopoName] .= "" . $smstext . "
\n";
}
}
## Add a PmWiki custom markup
# (:nztopo ll=-41.293722,174.871482 :)
# (:nztopo topo50=BP33912708,BP33876687 height=300 width=400 pin=1 label='destination' float=right clear=both zoom=12:)
# (:nztopo mapref=BN33991890:)
## the following builds the regex to parse the NZTopo PmWiki directive input parameters
# directive arguments are
# ll= -- decimal latitude,longitude
# llbs = -- -- decimal latitude,longitude;latitude,longitude
$vlat = '[-+]?\d{1,2}[.]\d+'; # latitude -90 .. 90
$vlong = '[-+]?[1]?\d{1,2}[.]\d+'; # longitude -180 .. 180
$vlatlong = $vlat . '[,]' . $vlong; # latitude,longitude
$pll = 'll=' . $vlatlong;
$pllbs = 'llbs=' . $vlatlong . '(?:[;]' . $vlatlong . ')+'; # two or more pairs of lat long co-ordinates separated by semicolons
# nztm
$vnztm = '\d{7}(?:[.]\d{0,3})?'; # single NZTM co-ordinate e.g. 1234567(.123)
$vnztm2 = $vnztm . '[,]' . $vnztm; # pair of NZTM co-ordinates: easting; northing, separated by comma
# mapref
$mapref ='[ABC][A-Z][0-4]\d[ ]?\d{6}'; # topo50 map reference e.g. BN33991890, BN33 991890
## parameters
$pnzne = 'nzne=' . $vnztm2;
$pnzbs = 'nzbs=' . $vnztm2 . '(?:[;]' . $vnztm2 . ')+'; # two or more pairs of NZTM co-ordinates separated by semicolons
# topo50= -- topo50 grid coordinate, or two grid coordinates (not currently implemented in nztopomap)
$ptopo50 = 'topo50=' . $mapref . '(?:[,]' . $mapref .')?'; # separated by comma
## -- only one of topo50, ll, llbs, nzne, nzbs,, kml or gpx can be supplied
$vscheme = 'https?:\/\/'; #
$vurl = "(?:(?:'" . $vscheme . "[^']+')" . # provide for single and double quoted URL strings, or no quotes
'|(?:"' . $vscheme . '[^"]+")' .
'|(?:' . $vscheme . '[^.?$#\s].[^\s\r\n]*))'; #from https://mathiasbynens.be/demo/url-regex
# kml= -- URL to kml file
$pkml = 'kml=' . $vurl;
if( $debugon ) { # add extra test parameters
$pkml2 = 'kml2=' . $vurl; # added for debug and testing
$pkml3 = 'kml3=' . $vurl; # added for debug and testing
$pkml4 = 'kml4=' . $vurl; # added for debug and testing
} # debugon
# gpx= -- URL to gpx file
$pgpx = 'gpx=' . $vurl;
# map reference
$pmapref = 'mapref=' . $mapref;
# height= -- image height in pixels
$pheight = 'height=\d{1,5}';
# width= -- image width in pixels
$pwidth = 'width=\d{1,5}';
# pin= -- show pin
$ppin = 'pin=[01]';
# label -- tool tip label for pin
$plabel = 'label=(?:(?:\'[^\']+\')|(?:"[^"]+")|(?:[-\w+!?()]+))'; # provide for single and double quoted strings, and unquoted string (no spaces)
# zoom= -- scale factor for map
$pzoom = 'zoom=\d{1,2}';
# float= -- left or right
$pfloat = 'float=(?:left|right)';
# clear= -- left, right, both
$pclear = 'clear=(?:left|right|both)';
# caption = string
$pcaption = 'caption=(?:(?:\'[^\']+\')|(?:"[^"]+")|(?:[-\w+!?()]+))'; # provide for single and double quoted strings, and unquoted string (no spaces)
## only one location can be supplied
$qlocale = '(?:' . $pll . ')|(?:' . $pllbs . ')|(?:' . $pkml . ')|(?:' . $pgpx . ')|(?:' . $pnzne . ')|(?:' . $pnzbs . ')|(?:' . $pmapref . ')';
if( $debugon ) { # add extra test parameters
$qlocale .= '|(?:' . $pkml2 . ')|(?:' . $pkml3 . ')|(?:' . $pkml4 . ')';
} # debugon
# display modifications
$qmods = "(" . $pheight . ")\s*|(" . $pwidth . ")\s*|(" . $ppin . ")\s*|(" . $plabel . ")\s*|(" . $pzoom . ")\s*|(" . $pfloat . ")\s*|(" . $pclear . ")\s*";
# captions
$qdesc = "(" . $pcaption . ")\s*";
##
Markup($NZTopoMUName, # an internal PmWiki function that defines the custom markup for the wiki (see https://www.pmwiki.org/wiki/PmWiki/CustomMarkup)
'directives',
"/\\(:" . mb_strtolower($NZTopoMUName) . ' (' . $qlocale . ")\s*(?:" . $qmods . "){0,7}\s*(?:" . $qdesc . "){0,1}\s*:\\)/i", #
"NZTopo_Parse" );
# s = dot matches all chars including newline
# i = case insensitive
# m = multiline
# uses lazy evaluation, preserves leading and trailing white space
# Keep prevents PmWiki markup being applied
#
/** Main NZTopo parser
* /param arguments as documented above
* /return The HTML-formatted NZTopo markup wrapped in a of class "nztopo", see https://www.topomap.co.nz/.
*/
function NZTopo_Parse($m):string {
list ( , $p1, $p2, $p3, $p4, $p5, $p6, $p7, $p8, $p9) = $m;
#
global $NZTopoZoom, $NZTopoNewwin; # configuration variables from configuration file, e.g. config.php
global $debugon; # for debug
// Initialise variables
$retval = ''; # return value
$opt = ParseArgs($p1 . ' ' . $p2 . ' ' . $p3 . ' ' . $p4 . ' ' . $p5 . ' ' . $p6 . ' ' . $p7 . ' ' . $p8 . ' ' . $p9); # see pmWiki documentation
$src_url = 'https://www.topomap.co.nz/NZTopoMap'; # URL for browser service
$src_urlemb = 'https://www.topomap.co.nz/NZTopoMapEmbedded'; # URL for embedded service
$src_proxy = 'https://www.topomap.co.nz/proxy.ashx?'; # URL for gpx proxy
$version = 'v=2'; # fixed version of nztopomap interface
$newwin = $NZTopoNewwin; # open map in new window
//
## need dpi to calculate size of map to ask for, have not been told what topomap generates
$ppcm = 80; # 80 pixels per centimetre to provide base scale for maps (guess)
//
$width = $opt['width']; # iframe parameter
$height = $opt['height']; # iframe parameter
$float = (bool) $opt['float'] ? 'float:' . $opt['float'] . ';' : ''; # span parameter
$clear = (bool) $opt['clear'] ? 'clear:' . $opt['clear'] . ';' : ''; # span parameter
$position = (bool) $clear . $float ? ' style="' . $clear . $float . '"' : ''; # span positioning
$debugval = $debugon ? '' : ''; # initialise
$dbgval = ''; # initialise empty
## query string parameters
$bllbs = (boolean) $opt['llbs'];
if( $bllbs ) { # get size of map in km
list ($widthkm, $heightkm, $dbgval) = llbskm($opt['llbs']);
$debugval .= $dbgval;
$width = max ($widthkm * $ppcm, $width); # override height
$height = max ($heightkm * $ppcm, $width); # override height
}
$bnzbs = (boolean) $opt['nzbs'];
if( $bnzbs ) { # get size of map in km
list ($widthkm, $heightkm, $dbgval) = nzbskm($opt['nzbs']);
$debugval .= $dbgval;
$width = max ($widthkm * $ppcm, $width); # override height
$height = max ($heightkm * $ppcm, $width); # override height
}
## build query string (i.e. topomap API)
$query = ''; # initialise
$query .= (bool) $opt['ll'] ? '&ll=' . $opt['ll'] : '';
$query .= (bool) $opt['llbs'] ? '&llbs=' . $opt['llbs'] : '';
$query .= (bool) $opt['topo50'] ? '&topo50=' . $opt['topo50'] : '';
$query .= (bool) $opt['nzbs'] ? '&nzbs=' . $opt['nzbs'] : '';
$query .= (bool) $opt['nzne'] ? '&nzne=' . $opt['nzne'] : '';
//
# trim URLs for spaces and quotes; rawurldecode URLs before rawurlencode them for nztopomap
# rawurldecode resets any pasted url to a 'known' state
//$query .= (bool) $opt['kml'] ? '&kml=' . rawurlencode(htmlentities(rawurldecode(trim($opt['kml'], ' \'\"')))) : ''; # does not comply with topomap API
$query .= (bool) $opt['kml'] ? '&kml=' . encode_kml($opt['kml'], $dbgval) : ''; # call function to comply with topomap API
$debugval .= (bool) $opt['kml'] ? $dbgval : ''; # debug output
if( $debugon ) { # apply different combinations of encoding for match with topomap API
# kml filename double rawurlencode (for debug and testing only)
$query .= (bool) $opt['kml2'] ? '&kml=' . rawurlencode(rawurlencode(htmlentities(rawurldecode(trim($opt['kml2'], ' \'\"'))))) : '';
# kml filename single rawurlencode with no rawurldecode (for debug and testing only)
$query .= (bool) $opt['kml3'] ? '&kml=' . rawurlencode(htmlentities(trim($opt['kml3'], ' \'\"'))) : '';
# kml filename double rawurlencode with no rawurldecode (for debug and testing only)
$query .= (bool) $opt['kml4'] ? '&kml=' . rawurlencode(rawurlencode(htmlentities(trim($opt['kml4'], ' \'\"')))) : '';
} # end debugon
// note gpx works fine
$query .= (bool) $opt['gpx'] ? '&gpx=' . rawurlencode($src_proxy . htmlentities(rawurldecode(trim($opt['gpx'], ' \'\"')))) : '';
//
$query .= (bool) $opt['mapref'] ? '&mapref=' . $opt['mapref'] : '';
$query .= (bool) $opt['pin'] ? '&pin=' . $opt['pin'] : '';
$query .= (bool) $opt['label'] ? '&lbl=' . htmlentities(trim($opt['label'])) : ''; # trim spaces, encode special chars for them to work with the topo map
if(! ($bnzbs OR $bllbs)) { # zoom cannot be a parameter for these options
$query .= (bool) $opt['zoom'] ? '&z=' . $opt['zoom'] : '&z=' . $NZTopoZoom;
}
$caption = (bool) $opt['caption'] ? trim($opt['caption']) : ''; # trim spaces
$query .= '&new=' . $newwin; # set open in new window option
$awidth = (bool) $width ? ' width="' . $width . '"' : ''; # iframe parameter
$aheight = (bool) $height ? ' height="' . $height . '"' : ''; # iframe parameter
$src = $src_urlemb . '?' . $version . $query;
$retval .= '' . "\n";
$retval .= '' . "\n";
if (!empty ($caption)) $retval .= '' . $caption . "\n";
$retval .= '' . "\n";
/* example query string for nztopomap https://www.topomap.co.nz/NZTopoMap or https://www.topomap.co.nz/NZTopoMapEmbedded
?v=2&ll=-41.293722,174.871482&z=15&pin=1
?v=2&kml=https%3A%2F%2Fkiwiwiki.co.nz%2Fpmwiki%2Fuploads%2FTest%2FNZTopo-Attach%2F29%2520May%25202015%252020_01_35.kml
?v=2&gpx=https%3A%2F%2Fwww.topomap.co.nz%2Fproxy.ashx%3Fhttps%3A%2F%2Fkiwiwiki.co.nz%2Fpmwiki%2Fuploads%2FTest%2FNZTopo-Attach%2F20150531.gpx
*/
if( $debugon ) { # display inputs and outputs to wiki page
$debugval .= 'input: p1=' . $p1 . ' p2=' . $p2 . ' p3=' . $p3 . ' p4=' . $p4 . ' p5=' . $p5 . ' p6=' . $p6 . ' p7=' . $p7 . ' p8=' . $p8 . ' p9=' . $p9 . '' . "\n";
$debugval .= 'output: src="' . '' . $version . $query . '';
$debugval .= '"' . "\n";
} # debugon
return Keep($retval . $debugval); # Keep prevents PmWiki markup being applied
} # end NZTopo_Parse
function encode_kml(string $kml_url, string &$dbgval):string {
global $debugon; # for debug
# for the kml parameter the topomap API is inconsistent. The kml path is urlencoded separately to the full url string
// specifically rawurlencode($src_proxy . rawurldecode(trim(filename.gpx, ' \'\"'))) works,
// but rawurlencode( rawurldecode(trim(filename.kml, ' \'\"'))) does not
$temp_url = # decode for parse_url and to start with known state (user may for example provide spaces in the URL string)
htmlentities( # protect against injection attacks
rawurldecode(trim($kml_url, ' \'\"'))); # trim single and double quotes and spaces
$parsed_url = parse_url($temp_url); # extract url components
$dbgval .= $debugon ? ' path="' . $parsed_url['path'] . '" ' . "\n" : ''; # debug output
$parsed_url['path'] = rawurlencode(substr($parsed_url['path'], 1)); # encode path separately again, dropping leading '/'
$dbgval .= $debugon ? ' enc_path="' . $parsed_url['path'] . '" ' . "\n" : ''; # debug output
$enc_url = ''; # reconstruct URL
$enc_url .= (bool) $parsed_url['scheme'] ? $parsed_url['scheme'] . '://' : ''; # aka http or https
$enc_url .= (bool) $parsed_url['host'] ? $parsed_url['host'] : ''; # domain name
$enc_url .= (bool) $parsed_url['port'] ? ':' . $parsed_url['port'] : ''; # port
$enc_url .= (bool) $parsed_url['path'] ? '/' . $parsed_url['path'] : ''; # add '/' dropped before as topomap considers it part of the host
# query and fragment omitted
return rawurlencode ($enc_url); # finally encode url and return it
} # end encode_kml
function llbskm(string $llbs):array {
global $debugon; # for debug
$latlngs = preg_split ('/[,;]/', $llbs);
$lats = array(); # eastings
$lngs = array(); # northings
foreach ($latlngs as $k => $v) {
$k % 2 == 0 ? $lats [] = $v : $lngs [] = $v;
}
$maxlat = max ($lats);
$maxlng = max ($lngs);
$minlat = min ($lats);
$minlng = min ($lngs);
/* rectangle coordinates are (lat/long)
left: right:
top: max/min max/max
bot: min/min min/max
*/
$width1 = distance ($maxlat, $minlng, $maxlat, $maxlng);
$width2 = distance ($minlat, $minlng, $minlat, $maxlng);
$height1 = distance ($maxlat, $minlng, $minlat, $minlng);
$height2 = distance ($maxlat, $maxlng, $minlat, $maxlng);
$widthkm = ceil (max ($width1, $width2) * 10) / 10; # take largest width and round up to km
$heightkm = ceil (max ($height1, $height2) * 10) / 10; # take largest height and round up to km
if( $debugon ) {
$dbgval = 'llbskm:';
$dbgval .= 'lats="' . var_export ($lats, TRUE) . '"' . "\n";
$dbgval .= 'lngs="' . implode ('", "', $lngs) . '"' . "\n";
$dbgval .= 'minlat=' . $minlat . ', maxlat=' . $maxlat . ', minlng=' . $minlng . ', maxlng=' . $maxlng . '' . "\n";
$dbgval .= 'w1=' . $width1 . ', w2=' . $width2 . ', h1=' . $height1 . ', h2=' . $height2 . ', w=' . $widthkm . 'km, h=' . $heightkm . 'km' . "\n";
} # debugon
return array ($widthkm, $heightkm, $dbgval);
} # end llbskm
function nzbskm(string $nzbs):array {
global $debugon; # for debug
$nthsests = preg_split ('/[,;]/', $nzbs); # https://www.linz.govt.nz/land/maps/linz-topographic-maps/topo50-maps/features-topo50-map/topo50-prototype-map-reading
$nths = array(); # northings
$ests = array(); # eastings
foreach ($nthsests as $ky => $val) {
$ky % 2 == 0 ? $nths [] = $val : $ests [] = $val;
}
$maxnth = max ($nths);
$maxest = max ($ests);
$minnth = min ($nths);
$minest = min ($ests);
/* rectangle coordinates are (nth/est)
left: right:
top: max/min max/max
bot: min/min min/max
*/
$width1 = ($maxest - $minest) / 1000; # metres to km
$height1 = ($maxnth - $minnth) / 1000; # metres to km
$widthkm = ceil ($width1 * 10) / 10; # round up to 0.1 km
$heightkm = ceil ($height1 * 10) / 10; # round up to 0.1 km
if( $debugon ) {
$dbgval = 'nzbskm:';
$dbgval .= 'nthsests="' . implode ('", "', $nthsests) . '"' . "\n";
$dbgval .= 'ests="' . implode ('", "', $ests) . '"' . "\n";
$dbgval .= 'nths="' . implode ('", "', $nths) . '"' . "\n";
$dbgval .= 'minest=' . $minest . ', maxest=' . $maxest . ', minnth=' . $minnth . ', maxnth=' . $maxnth . '' . "\n";
$dbgval .= 'w1=' . $width1 . ', h1=' . $height1 . ', w=' . $widthkm . 'km, h=' . $heightkm . 'km' . "\n";
} # debugon
return array ($widthkm, $heightkm, $dbgval);
} # end nzbskm
# https://web.archive.org/web/20170604082154/http://snipplr.com/view/2531, https://inkplant.com/code/calculate-the-distance-between-two-points
function distance ($lat1, $lng1, $lat2, $lng2):float
# convert distance in km between two lat longs
{
$pi80 = M_PI / 180;
$lat1 *= $pi80;
$lng1 *= $pi80;
$lat2 *= $pi80;
$lng2 *= $pi80;
$r = 6372.797; // mean radius of Earth in km
$dlat = $lat2 - $lat1;
$dlng = $lng2 - $lng1;
$a = sin($dlat / 2) * sin($dlat / 2) + cos($lat1) * cos($lat2) * sin($dlng / 2) * sin($dlng / 2);
$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
$km = $r * $c;
return $km;
} # end distance