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