1<?php 2 3class WorldmapError extends MapSourceError {} 4 5define('MATCH_WORLDMAP_ZOOM', '/^1?[0-9]|20$/'); 6 7// Register this source as being selectable by the user 8global $selectable; 9$selectable = true; 10 11// options to be modifiable by the user(url) 12global $viewParams; 13$viewParams = array( 14 'worldmap' => array( 15 'backend_id', 16 'worldmap_center', 17 'worldmap_zoom', 18 'filter_group', 19 ) 20); 21 22// Config variables to be registered for this source 23global $configVars; 24$configVars = array( 25 /*** GLOBAL OPTIONS ***/ 26 'worldmap_center' => array( 27 'must' => true, 28 'default' => '50.86837814203458,10.21728515625', 29 'match' => MATCH_LATLONG, 30 ), 31 'worldmap_zoom' => array( 32 'must' => true, 33 'default' => 6, 34 'match' => MATCH_WORLDMAP_ZOOM, 35 ), 36 'worldmap_tiles_saturate' => array( 37 'must' => false, 38 'default' => '', 39 'match' => MATCH_INTEGER_EMPTY, 40 ), 41 42 /*** OBJECT OPTIONS ***/ 43 'min_zoom' => array( 44 'must' => false, 45 'default' => 2, 46 'match' => MATCH_WORLDMAP_ZOOM, 47 ), 48 'max_zoom' => array( 49 'must' => false, 50 'default' => 20, 51 'match' => MATCH_WORLDMAP_ZOOM, 52 ), 53 54 'scale_to_zoom' => Array( 55 'must' => 0, 56 'default' => 0, 57 'match' => MATCH_BOOLEAN, 58 'field_type' => 'boolean', 59 ), 60 'normal_size_at_zoom' => array( 61 'must' => false, 62 'default' => 19, 63 'match' => MATCH_WORLDMAP_ZOOM, 64 ), 65 66); 67 68// Assign config variables to specific object types 69global $configVarMap; 70$configVarMap = array( 71 'global' => array( 72 'worldmap' => array( 73 'worldmap_center' => null, 74 'worldmap_zoom' => null, 75 'worldmap_tiles_saturate' => null, 76 ), 77 ), 78); 79 80// Assign these options to all map objects (except global) 81foreach (getMapObjectTypes() AS $type) { 82 $configVarMap[$type] = array( 83 'worldmap' => array( 84 'min_zoom' => null, 85 'max_zoom' => null, 86 ), 87 ); 88} 89 90// Textbox-specific options 91$configVarMap['textbox']['worldmap'] = array_merge($configVarMap['textbox']['worldmap'], array( 92 'scale_to_zoom' => null, 93 'normal_size_at_zoom' => null, 94)); 95 96// Global config vars not to show for worldmaps 97$hiddenConfigVars = array( 98 'zoom', 99 'zoombar', 100); 101 102// Alter some global vars with automap specific things 103$updateConfigVars = array( 104 'iconset' => array( 105 'default' => 'std_geo', 106 ), 107 'icon_size' => array( 108 'default' => array(24), 109 ), 110); 111 112// The worldmap database object 113$DB = null; 114 115function worldmap_init_schema() { 116 global $DB, $CORE; 117 // Create initial db scheme if needed 118 if (!$DB->tableExist('objects')) { 119 $DB->query('CREATE TABLE objects ' 120 .'(object_id VARCHAR(20),' 121 .' lat REAL,' 122 .' lng REAL,' 123 .' lat2 REAL,' // needed for line objects 124 .' lng2 REAL,' 125 .' object TEXT,' 126 .' PRIMARY KEY(object_id))'); 127 $DB->query('CREATE INDEX latlng ON objects (lat,long)'); 128 $DB->query('CREATE INDEX latlng2 ON objects (lat2,long2)'); 129 $DB->createVersionTable(); 130 131 // Install demo data 132 worldmap_db_update_object('273924', 53.5749514424993, 10.0405490398407, array( 133 "x" => "53.57495144249931", 134 "y" => "10.040549039840698", 135 "type" => "map", 136 "map_name" => "demo-ham-racks", 137 "object_id" => "273924" 138 )); 139 worldmap_db_update_object('0df2d3', 48.1125317248817, 11.6794109344482, array( 140 "x" => "48.11253172488166", 141 "y" => "11.67941093444824", 142 "type" => "hostgroup", 143 "hostgroup_name" => "muc", 144 "object_id" => "0df2d3" 145 )); 146 worldmap_db_update_object('ebbf59', 50.9391761712781, 6.95863723754883, array( 147 "x" => "50.93917617127812", 148 "y" => "6.958637237548828", 149 "type" => "hostgroup", 150 "hostgroup_name" => "cgn", 151 "object_id" => "ebbf59" 152 )); 153 } 154 //else { 155 // // Maybe an update is needed 156 // $DB->updateDb(); 157 158 // // Only apply the new version when this is the real release or newer 159 // // (While development the version string remains on the old value) 160 // //if($CORE->versionToTag(CONST_VERSION) >= 1060100) 161 // $DB->updateDbVersion(); 162 //} 163} 164 165function worldmap_init_db() { 166 global $DB; 167 if ($DB !== null) 168 return; // only init once 169 $DB = new CorePDOHandler(); 170 if (!$DB->open('sqlite', array('filename' => cfg('paths', 'cfg').'worldmap.db'), null, null)) 171 throw new NagVisException(l('Unable to open worldmap database ([DB]): [MSG]', 172 Array('DB' => $DB->getDSN(), 173 'MSG' => json_encode($DB->error())))); 174 175 worldmap_init_schema(); 176} 177 178// compuetes 2D line constants from a segment (2 points) 179// returns parameters r,s,t of the common 2D "rx + sy + t = 0" equation 180function line_parameters($ax, $ay, $bx, $by) { 181 // segment vector 182 $ux = $bx - $ax; 183 $uy = $by - $ay; 184 185 // perpendicular vector 186 $nx = $uy; 187 $ny = -$ux; 188 189 // r, s, t constants 190 $r = $nx; 191 $s = $ny; 192 $t = -($r*$ax + $s*$ay); 193 194 if ($s == -0) $s = 0; 195 196 return array($r, $s, $t); 197} 198function worldmap_get_objects_by_bounds($sw_lng, $sw_lat, $ne_lng, $ne_lat) { 199 global $DB; 200 worldmap_init_db(); 201 202 if ($sw_lat > $ne_lat) swap($sw_lat, $ne_lat); 203 if ($sw_lng > $ne_lng) swap($sw_lng, $ne_lng); 204 205 // The 4 bounding lines expressed as common 2D "rx + sy + t = 0" equations 206 list($rWest, $sWest, $tWest) = line_parameters($sw_lng, $sw_lat, $sw_lng, $ne_lat); 207 list($rNorth, $sNorth, $tNorth) = line_parameters($sw_lng, $ne_lat, $ne_lng, $ne_lat); 208 list($rEast, $sEast, $tEast) = line_parameters($ne_lng, $sw_lat, $ne_lng, $ne_lat); 209 list($rSouth, $sSouth, $tSouth) = line_parameters($sw_lng, $sw_lat, $ne_lng, $sw_lat); 210 211 /* SQLite 2D line equations */ 212 $ux = '(lng2-lng)'; 213 $uy = '(lat2-lat)'; 214 $nx = $uy; 215 $ny = "(-($ux))"; 216 $r = "($nx)"; 217 $s = "($ny)"; 218 $t = "(-$r*lng-$s*lat)"; 219 220 // y-coordinate of line-vs-west-edge intersection 221 $intyWest = "($rWest*$t - ($tWest)*$r)/($sWest*$r - ($rWest)*$s)"; 222 $intWithinWestBound = "($intyWest between :sw_lat and :ne_lat AND min(lng,lng2) <= :sw_lng AND max(lng,lng2) >= :sw_lng)"; 223 224 // x-coordinate of line-vs-south-edge intersection 225 $intxSouth = "($s*$tSouth-($sSouth)*$t)/($sSouth*$r-$s*$rSouth)"; 226 $intWithinSouthBound = "($intxSouth between :sw_lng and :ne_lng AND min(lat,lat2) <= :sw_lat AND max(lat,lat2) >= :sw_lat)"; 227 228 // y-coordinate of line-vs-east-edge intersection 229 $intyEast = "($rEast*$t - ($tEast)*$r)/($sEast*$r - ($rEast)*$s)"; 230 $intWithinEastBound = "($intyEast between :sw_lat and :ne_lat AND min(lng,lng2) <= :ne_lng AND max(lng,lng2) >= :ne_lng)"; 231 232 // x-coordinate of line-vs-north-edge intersection 233 $intxNorth = "($s*$tNorth-($sNorth)*$t)/($sNorth*$r-$s*$rNorth)"; 234 $intWithinNorthBound = "($intxNorth between :sw_lng and :ne_lng AND min(lat,lat2) <= :ne_lat AND max(lat,lat2) >= :ne_lat)"; 235 236 $q = 'SELECT lat, lng, lat2, lng2, object FROM objects WHERE' 237 // object lays, or line starts within bbox 238 .'(lat BETWEEN :sw_lat AND :ne_lat AND lng BETWEEN :sw_lng AND :ne_lng)' 239 // line ends within bbox 240 .'OR (lat2 BETWEEN :sw_lat AND :ne_lat AND lng2 BETWEEN :sw_lng AND :ne_lng)' 241 // line intersects one of 4 bbox borders 242 ."OR (lat2>0 AND lng2>0 AND ($intWithinWestBound OR $intWithinSouthBound OR $intWithinEastBound OR $intWithinNorthBound))" 243 ; 244 245 $q = str_replace(':sw_lng', $sw_lng, $q); 246 $q = str_replace(':sw_lat', $sw_lat, $q); 247 $q = str_replace(':ne_lng', $ne_lng, $q); 248 $q = str_replace(':ne_lat', $ne_lat, $q); 249 // error_log("Query objects: $q"); 250 251 $RES = $DB->query($q); 252 253 if ($RES == false) { 254 error_log(implode($DB->error(),',')); 255 throw new WorldmapError(l('Failed to fetch objects: [E]; Query was [Q]', array( 256 'E' => json_encode($DB->error()), 'Q' => $q))); 257 } 258 259 $objects = array(); 260 $referenced = array(); 261 while ($data = $RES->fetch()) { 262 $obj = json_decode($data['object'], true); 263 $objects[$obj['object_id']] = $obj; 264 // check all coordinates for relative coords 265 $coords = array($data['lat'], $data['lng'], $data['lat2'], $data['lng2']); 266 foreach ($coords as $coord) { 267 if (strpos($coord, '%') !== false) { 268 $referenced[substr($coord, 0, 6)] = null; 269 } 270 } 271 } 272 273 // When an object has relative coordinates also fetch the referenced object 274 if ($referenced) { 275 $keys = array_unique(array_keys($referenced)); 276 $count = count($keys); 277 $oids = array(); 278 $filter = array(); 279 for ($i = 1; $i <= $count; $i++) { 280 $id = ":o$i"; 281 $oids[] = $id; 282 $filter[$id] = $keys[$i - 1]; 283 } 284 $q = 'SELECT object FROM objects WHERE object_id IN ('.implode(', ', $oids).')'; 285 $RES = $DB->query($q, $filter); 286 while ($data = $RES->fetch()) { 287 $obj = json_decode($data['object'], true); 288 $objects[$obj['object_id']] = $obj; 289 } 290 } 291 292 return $objects; 293} 294 295function worldmap_get_object_by_id($id) { 296 global $DB; 297 worldmap_init_db(); 298 299 $q = 'SELECT object FROM objects WHERE object_id = :id LIMIT 1'; 300 301 $RES = $DB->query($q, array('id' => $id)); 302 if ($data = $RES->fetch()) { 303 return json_decode($data['object'], true); 304 } 305} 306 307// Worldmap internal helper function to add an object to the worldmap 308function worldmap_db_update_object($obj_id, $lat, $lng, $obj, 309 $lat2 = null, $lng2 = null, $insert = true) { 310 global $DB; 311 worldmap_init_db(); 312 313 if ($insert) { 314 $q = 'INSERT INTO objects (object_id, lat, lng, lat2, lng2, object)' 315 .' VALUES (:obj_id, :lat, :lng, :lat2, :lng2, :object)'; 316 } 317 else { 318 $q = 'UPDATE objects SET ' 319 .' lat=:lat,' 320 .' lng=:lng,' 321 .' lat2=:lat2,' 322 .' lng2=:lng2,' 323 .' object=:object ' 324 .'WHERE object_id=:obj_id'; 325 } 326 327 if ($DB->query($q, array( 328 'obj_id' => $obj_id, 329 'lat' => $lat, 330 'lng' => $lng, 331 'lat2' => $lat2, 332 'lng2' => $lng2, 333 'object' => json_encode($obj)))) 334 return true; 335 else 336 throw new WorldmapError(l('Failed to add object: [E]: [Q]', array( 337 'E' => json_encode($DB->error()), 'Q' => $q))); 338} 339 340function worldmap_update_object($MAPCFG, $map_name, &$map_config, $obj_id, $insert = true) { 341 $obj = $map_config[$obj_id]; 342 343 if ($obj['type'] == 'global') 344 return false; // adding global section (during map creation) 345 346 // disable creating new objects during "view to new map" action 347 if (val($_GET, 'act', null) == 'viewToNewMap') 348 return true; 349 350 $lat = $obj['x']; 351 $lng = $obj['y']; 352 $lat2 = null; 353 $lng2 = null; 354 355 // Handle lines and so on 356 if ($MAPCFG->getValue($obj_id, 'view_type') == 'line' || $obj['type'] == 'line') { 357 $x = explode(',', $obj['x']); 358 $y = explode(',', $obj['y']); 359 $lat = $x[0]; 360 $lng = $y[0]; 361 $lat2 = $x[count($x)-1]; 362 $lng2 = $y[count($y)-1]; 363 } 364 365 return worldmap_db_update_object($obj_id, $lat, $lng, $obj, $lat2, $lng2, $insert); 366} 367 368// 369// The following functions are used directly by NagVis 370// 371 372// Set the needed values for maps currently being created 373function init_map_worldmap($MAPCFG, $map_name, &$map_config) { 374 global $configVars; 375 $MAPCFG->setValue(0, "worldmap_center", $configVars["worldmap_center"]["default"]); 376 $MAPCFG->setValue(0, "worldmap_zoom", $configVars["worldmap_zoom"]["default"]); 377} 378 379// Returns the minimum bounds needed to be able to display all objects 380function get_bounds_worldmap($MAPCFG, $map_name, &$map_config) { 381 global $DB; 382 worldmap_init_db(); 383 384 $q = 'SELECT min(lat) as min_lat, min(lng) as min_lng, ' 385 .'max(lat) as max_lat, max(lng) as max_lng ' 386 .'FROM objects'; 387 $b = $DB->query($q)->fetch(); 388 return array(array($b['min_lat'], $b['min_lng']), 389 array($b['max_lat'], $b['max_lng'])); 390} 391 392function load_obj_worldmap($MAPCFG, $map_name, &$map_config, $obj_id) { 393 global $DB; 394 worldmap_init_db(); 395 396 if (isset($map_config[$obj_id])) 397 return true; // already loaded 398 399 $q = 'SELECT object FROM objects WHERE object_id=:obj_id'; 400 $b = $DB->query($q, array('obj_id' => $obj_id))->fetch(); 401 if ($b) 402 $map_config[$obj_id] = json_decode($b['object'], true); 403} 404 405function del_obj_worldmap($MAPCFG, $map_name, &$map_config, $obj_id) { 406 global $DB; 407 worldmap_init_db(); 408 409 $q = 'DELETE FROM objects WHERE object_id=:obj_id'; 410 if ($DB->query($q, array('obj_id' => $obj_id))) 411 return true; 412 else 413 throw new WorldmapError(l('Failed to delete object: [E]: [Q]', array( 414 'E' => json_encode($DB->error()), 'Q' => $q))); 415} 416 417function update_obj_worldmap($MAPCFG, $map_name, &$map_config, $obj_id) { 418 return worldmap_update_object($MAPCFG, $map_name, $map_config, $obj_id, false); 419} 420 421function add_obj_worldmap($MAPCFG, $map_name, &$map_config, $obj_id) { 422 return worldmap_update_object($MAPCFG, $map_name, $map_config, $obj_id); 423} 424 425function process_worldmap($MAPCFG, $map_name, &$map_config) { 426 $bbox = val($_GET, 'bbox', null); 427 $clone_id = val($_GET, 'clone_id', null); 428 429 if ($bbox !== null) 430 { 431 $params = $MAPCFG->getSourceParams(); 432 $zoom = (int)$params['worldmap_zoom']; 433 434 list($sw_lng, $sw_lat, $ne_lng, $ne_lat) = explode(',', $bbox); 435 foreach (worldmap_get_objects_by_bounds($sw_lng, $sw_lat, $ne_lng, $ne_lat) as $object_id => $obj) { 436 // Now, when the object has a maximum / minimum zoom configured, 437 // hide it depending on the zoom 438 $min_zoom = isset($obj['min_zoom']) ? (int)$obj['min_zoom'] : $MAPCFG->getDefaultValue('host', 'min_zoom'); 439 $max_zoom = isset($obj['max_zoom']) ? (int)$obj['max_zoom'] : $MAPCFG->getDefaultValue('host', 'max_zoom'); 440 441 if ($min_zoom <= $max_zoom && ($zoom < $min_zoom || $zoom > $max_zoom)) 442 continue; 443 444 $map_config[$object_id] = $obj; 445 } 446 447 return true; 448 } 449 elseif ($clone_id !== null) 450 { 451 $map_config[$clone_id] = worldmap_get_object_by_id($clone_id); 452 return true; 453 } 454 455 return false; 456} 457 458function changed_worldmap($MAPCFG, $compare_time) { 459 $db_path = cfg('paths', 'cfg').'worldmap.db'; 460 return !file_exists($db_path) || filemtime($db_path) > $compare_time; 461} 462 463function swap(&$x, &$y) { 464 $tmp=$x; 465 $x=$y; 466 $y=$tmp; 467} 468 469?> 470