2 * Copyright (c) 2016 highstreet technologies GmbH and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
10 * @typedef {{id: string, siteLink: string, radio: string, polarization: string }} AirInterfaceLink
11 * @typedef {{id: string, siteA: string, siteZ: string, siteNameA: string, siteNameZ: string, airInterfaceLinks: AirInterfaceLink[] }} DbLink
12 * @typedef {{data: {hits: {hits: {_id: string, _index: string, _score: number, _source: DbLink, _type: string}[], max_score: number, total: number}}, status: number}} DbLinkResult */
14 define(['app/mwtnTopology/mwtnTopology.module'], function (mwtnTopologyApp) {
15 // module.exports = function () {
16 // const mwtnTopologyApp = require('app/mwtnTopology/mwtnTopology.module');
17 // const mwtnTopologyCommons = require('app/mwtnCommons/mwtnCommons.service');
19 mwtnTopologyApp.factory('$mwtnTopology', function ($q, $mwtnCommons, $mwtnDatabase, $mwtnLog) {
22 // AF/MF: Obsolete - will removed soon. All data access function
23 service.getRequiredNetworkElements = $mwtnCommons.getRequiredNetworkElements;
24 service.gridOptions = $mwtnCommons.gridOptions;
25 service.highlightFilteredHeader = $mwtnCommons.highlightFilteredHeader;
26 service.getAllData = $mwtnDatabase.getAllData;
30 * Since not all browsers implement this we have our own utility that will
31 * convert from degrees into radians
33 * @param deg - The degrees to be converted into radians
36 var _toRad = function (deg) {
37 return deg * Math.PI / 180;
41 * Since not all browsers implement this we have our own utility that will
42 * convert from radians into degrees
44 * @param rad - The radians to be converted into degrees
47 var _toDeg = function (rad) {
48 return rad * 180 / Math.PI;
53 * Calculate the bearing between two positions as a value from 0-360
55 * @param lat1 - The latitude of the first position
56 * @param lng1 - The longitude of the first position
57 * @param lat2 - The latitude of the second position
58 * @param lng2 - The longitude of the second position
60 * @return int - The bearing between 0 and 360
62 service.bearing = function (lat1, lng1, lat2, lng2) {
63 var dLon = (lng2 - lng1);
64 var y = Math.sin(dLon) * Math.cos(lat2);
65 var x = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) * Math.cos(lat2) * Math.cos(dLon);
66 var brng = _toDeg(Math.atan2(y, x));
67 return 360 - ((brng + 360) % 360);
72 * Gets the geospatial distance between two points
73 * @param lat1 {number} The latitude of the first point.
74 * @param lon1 {number} The longitude of the first point.
75 * @param lat2 {number} The latitude of the second point.
76 * @param lon2 {number} The longitude of the second point.
77 * @returns {number} The distance between the two given points.
79 service.getDistance = function (lat1, lon1, lat2, lon2) {
81 var φ1 = _toRad(lat1);
82 var φ2 = _toRad(lat2);
83 var Δφ = _toRad(lat2 - lat1);
84 var Δλ = _toRad(lon2 - lon1);
86 var a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
87 Math.cos(φ1) * Math.cos(φ2) *
88 Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
89 var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
91 return (R * c).toFixed(3);
95 * Gets a promise which is resolved if the database has been calculated the bounds containing all sites.
96 * @returns {promise} The promise which is resolved if the database has completed its calculation.
98 service.getOuterBoundingRectangleForSites = function () {
99 var getOuterBoundingRectangleForSitesDefer = $q.defer();
104 "field": "location.lat"
109 "field": "location.lon"
114 "field": "location.lat"
119 "field": "location.lon"
126 $mwtnDatabase.getAggregatedData('mwtn', 'site', aggregation).then(function (result) {
127 getOuterBoundingRectangleForSitesDefer.resolve({
128 top: result.data.aggregations.top.value,
129 right: result.data.aggregations.right.value,
130 bottom: result.data.aggregations.bottom.value,
131 left: result.data.aggregations.left.value
133 }, function (error) {
134 getOuterBoundingRectangleForSitesDefer.reject(error);
137 return getOuterBoundingRectangleForSitesDefer.promise;
141 * Gets a promise which resolved with an array of sites within the given bounding box.
142 * @param boundingBox {{top: number, right: number, bottom: number, left: number}} The bounding box to get all sites for.
143 * @param chunkSize {number} The maximum count of sites who should return.
144 * @param chunkSiteStartIndex {number} The index of the first site element to get.
146 service.getSitesInBoundingBox = function (boundingBox, chunkSize, chunkSiteStartIndex) {
147 var resultDefer = $q.defer();
150 "geo_bounding_box": {
152 "top": boundingBox.top,
153 "right": boundingBox.right,
154 "bottom": boundingBox.bottom,
155 "left": boundingBox.left
160 $mwtnDatabase.getFilteredData("mwtn", "site", chunkSiteStartIndex, chunkSize, filter)
161 .then(processResult, resultDefer.reject);
163 return resultDefer.promise;
166 * Callback for the database request.
167 * @param result {{data: {hits: {hits: {_id: string, _index: string, _score: number, _source: {id: string, name: string, location: {lat: number, lon: number}, "amsl-ground": number, references: {"network-elements": string[], "site-links": string[]}}, _type: string}[], max_score: number, total: number}}, status: number}} The database result.
169 function processResult(result) {
170 var hits = result && result.data && result.data.hits;
172 resultDefer.reject("Invalid result.");
176 var total = hits.total;
179 for (var hitIndex = 0; hitIndex < hits.hits.length; ++hitIndex) {
180 var site = hits.hits[hitIndex];
183 name: site._source.name,
184 type: site._source.type,
186 lat: site._source.location.lat,
187 lng: site._source.location.lon
189 amslGround: site._source["amsl-ground"],
191 siteLinks: site._source.references["site-links"]
196 resultDefer.resolve({
197 chunkSize: chunkSize,
198 chunkSiteStartIndex: chunkSiteStartIndex,
206 * Gets a promise which is resolved with an array of sites filtered by given site ids.
207 * This function does not use chunks!
208 * @param siteIds {string[]} The ids of the sites to return.
210 service.getSitesByIds = function (siteIds) {
211 var resultDefer = $q.defer();
213 if (!siteIds || siteIds.length === 0) {
214 resultDefer.resolve([]);
215 return resultDefer.promise;
220 should: siteIds.map(function (siteId) {
221 return { term: { id: siteId } };
226 $mwtnDatabase.getFilteredData("mwtn", "site", 0, siteIds.length, query)
227 .then(processResult, resultDefer.reject);
229 return resultDefer.promise;
232 * Callback for the database request.
233 * @param result {{data: {hits: {hits: {_id: string, _index: string, _score: number, _source: {id: string, name: string, location: {lat: number, lon: number}, "amsl-ground": number, references: {"network-elements": string[], "site-links": string[]}}, _type: string}[], max_score: number, total: number}}, status: number}} The database result.
235 function processResult(result) {
236 var hits = result && result.data && result.data.hits;
238 resultDefer.reject("Invalid result.");
242 var total = hits.total;
245 for (var hitIndex = 0; hitIndex < hits.hits.length; ++hitIndex) {
246 var site = hits.hits[hitIndex];
249 name: site._source.name,
250 type: site._source.type,
252 lat: site._source.location.lat,
253 lng: site._source.location.lon
255 amslGround: site._source["amsl-ground"],
257 siteLinks: site._source.references["site-links"]
262 resultDefer.resolve({
270 * Gets a promise which is resolved with an array of sites using the given filter expression.
272 service.getSites = function (sortColumn, sortDirection, filters, chunkSize, chunkSiteStartIndex) {
273 var resultDefer = $q.defer();
275 // determine the sort parameter
277 if (sortColumn != null && sortDirection != null) {
279 switch (sortColumn) {
280 case 'countNetworkElements':
285 sort['amsl-ground'] = {
286 order: sortDirection === 'desc' ? 'desc' : 'asc'
291 order: sortDirection === 'desc' ? 'desc' : 'asc'
297 // determine the query parameter
299 if (filters == null || filters.length == 0) {
300 query["match_all"] = {};
303 filters.forEach(function (filter) {
304 if (filter && filter.field) {
305 regexp[filter.field] = '.*'+ filter.term + '.*';
308 query["regexp"] = regexp;
313 $mwtnDatabase.getFilteredSortedData("mwtn", "site", chunkSiteStartIndex, chunkSize, sort, query).then(processResult, resultDefer.reject);
315 $mwtnDatabase.getFilteredData("mwtn", "site", chunkSiteStartIndex, chunkSize, query).then(processResult, resultDefer.reject);
318 return resultDefer.promise;
321 * Callback for the database request.
322 * @param result {{data: {hits: {hits: {_id: string, _index: string, _score: number, _source: {id: string, name: string, location: {lat: number, lon: number}, "amsl-ground": number, references: {"network-elements": string[], "site-links": string[]}}, _type: string}[], max_score: number, total: number}}, status: number}} The database result.
324 function processResult(result) {
325 var hits = result && result.data && result.data.hits;
327 resultDefer.reject("Invalid result.");
331 var total = hits.total;
334 for (var hitIndex = 0; hitIndex < hits.hits.length; ++hitIndex) {
335 var site = hits.hits[hitIndex];
338 name: site._source.name,
339 type: site._source.type,
341 lat: site._source.location.lat,
342 lng: site._source.location.lon
344 amslGround: site._source["amsl-ground"],
346 siteLinks: site._source.references["site-links"]
351 resultDefer.resolve({
359 * Gets a promise which resolved with an array of site links referenced by given sites.
360 * @param sites {({id: string, name: string, location: {lat: number, lng: number}, amslGround: number, references: {siteLinks: string[]})[]}
361 * @param chunkSize {number} The maximum count of site links who should return.
362 * @param chunkSiteLinkStartIndex {number} The index of the first site link element to get.
364 service.getSiteLinksForSites = function (sites, chunkSize, chunkSiteLinkStartIndex) {
365 var resultDefer = $q.defer();
367 if (!sites || sites.length === 0) {
368 resultDefer.resolve([]);
369 return resultDefer.promise;
372 var siteLinkIds = Object.keys(sites.reduce(function (accumulator, currentSite) {
373 // Add all site link ids referenced by the current site to the accumulator object.
374 currentSite.references.siteLinks.forEach(function (siteLinkId) {
375 // The value "true"" isnt important, i only use the key (siteLinkId) later.
376 // But this way i dont have to check, if the key is already known.
377 accumulator[siteLinkId] = true;
380 }, {})).map(function (siteLinkId) {
381 return { term: { id: siteLinkId } };
385 bool: { should: siteLinkIds }
388 $mwtnDatabase.getFilteredData("mwtn", "site-link", chunkSiteLinkStartIndex, chunkSize, query).then(
390 * Callback for the database request.
391 * @param result {{data: {hits: {hits: {_id: string, _index: string, _score: number, _source: {id: string, siteA: string, siteZ: string, siteNameA: string, siteNameZ: string, airInterfaceLinks: {id: string, siteLink: string, radio: string, polarization: string }[] }, _type: string}[], max_score: number, total: number}}, status: number}} The database result.
394 var hits = result && result.data && result.data.hits;
396 resultDefer.reject("Invalid result.");
400 if (hits.total === 0) {
401 resultDefer.resolve([]);
405 // get additional sites that wont be given in the sites array but are referenced by the site links.
406 // get all sites, referenced by the site links.
407 var allReferencedSiteIds = hits.hits.reduce(function (accumulator, currentSiteLink) {
408 accumulator[currentSiteLink._source.siteA] = true;
409 accumulator[currentSiteLink._source.siteZ] = true;
412 // remove all known sites
413 sites.forEach(function (site) {
414 if (allReferencedSiteIds.hasOwnProperty(site.id)) {
415 delete allReferencedSiteIds[site.id];
419 var additionalReferencedSiteIds = Object.keys(allReferencedSiteIds).map(function (referencedSiteId) {
420 return { term: { id: referencedSiteId } };
423 if (additionalReferencedSiteIds.length > 0) {
425 bool: { should: additionalReferencedSiteIds }
428 $mwtnDatabase.getFilteredData("mwtn", "site", 0, 400, query).then(
430 * Callback for the database request.
431 * @param result {{data: {hits: {hits: {_id: string, _index: string, _score: number, _source: {id: string, name: string, location: {lat: number, lon: number}, "amsl-ground": number, references: {"network-elements": string[], "site-links": string[]}}, _type: string}[], max_score: number, total: number}}, status: number}} The database result.
434 var siteHits = result && result.data && result.data.hits;
436 resultDefer.reject("Invalid result.");
440 var additionalSites = siteHits.hits.map(function (site) {
443 name: site._source.name,
444 type: site._source.type,
446 lat: site._source.location.lat,
447 lng: site._source.location.lon
449 amslGround: site._source["amsl-ground"],
450 type: site._source.type,
452 siteLinks: site._source.references["site-links"]
457 var siteLinks = hits.hits.map(function (siteLink) {
459 id: siteLink._source.id,
460 siteA: sites.find(function (site) { return site.id === siteLink._source.siteA; }) || additionalSites.find(function (site) { return site.id === siteLink._source.siteA; }),
461 siteZ: sites.find(function (site) { return site.id === siteLink._source.siteZ; }) || additionalSites.find(function (site) { return site.id === siteLink._source.siteZ; }),
462 type: siteLink._source.type,
463 length: 5000 // AF/MF: the length will be served from the database in the next version.
467 resultDefer.resolve(siteLinks);
474 var siteLinks = hits.hits.map(function (siteLink) {
476 id: siteLink._source.id,
477 siteA: sites.find(function (site) { return site.id === siteLink._source.siteA; }),
478 siteZ: sites.find(function (site) { return site.id === siteLink._source.siteZ; }),
479 type: siteLink._source.type,
480 length: 5000 // AF/MF: the length will be served from the database in the next version.
484 resultDefer.resolve(siteLinks);
488 return resultDefer.promise;
492 * Gets a promise which is resolved with an array of planned filtered by given network element ids.
493 * This function does not use chunks!
494 * @param neIds {string[]} The ids of the site links to return.
496 service.getPlannedNetworkElementsByIds = function (neIds) {
497 var resultDefer = $q.defer();
499 if (!neIds || neIds.length === 0) {
500 resultDefer.resolve([]);
501 return resultDefer.promise;
506 should: neIds.map(function (neId) {
507 return { term: { id: neId } };
512 $mwtnDatabase.getFilteredData("mwtn", "planned-network-elements", 0, neIds.length, query).then(
514 * Callback for the database request.
515 * @param result {{data: {hits: {hits: {_id: string, _index: string, _score: number, _source: {id: string, name: string, type: string}, _type: string}[], max_score: number, total: number}}, status: number}} The database result.
518 var hits = result && result.data && result.data.hits;
520 resultDefer.reject("Invalid result.");
524 if (hits.total === 0) {
525 resultDefer.resolve([]);
529 var plannedNetworkElements = hits.hits.map(function (plannedNetworkElement) {
531 id: plannedNetworkElement._source.id,
532 name: plannedNetworkElement._source.name,
533 type: plannedNetworkElement._source.radioType
537 resultDefer.resolve(plannedNetworkElements);
538 }, resultDefer.reject);
540 return resultDefer.promise;
545 * Gets a promise which is resolved with an array of site links filtered by given site link ids.
546 * This function does not use chunks!
547 * @param siteLinkIds {string[]} The ids of the site links to return.
549 service.getSiteLinksByIds = function (siteLinkIds) {
550 var resultDefer = $q.defer();
552 if (!siteLinkIds || siteLinkIds.length === 0) {
553 resultDefer.resolve([]);
554 return resultDefer.promise;
559 should: siteLinkIds.map(function (siteLinkId) {
560 return { term: { id: siteLinkId } };
565 $mwtnDatabase.getFilteredData("mwtn", "site-link", 0, siteLinkIds.length, query).then(
567 * Callback for the database request.
568 * @param result {{data: {hits: {hits: {_id: string, _index: string, _score: number, _source: {id: string, siteA: string, siteZ: string}, _type: string}[], max_score: number, total: number}}, status: number}} The database result.
571 var hits = result && result.data && result.data.hits;
573 resultDefer.reject("Invalid result.");
577 if (hits.total === 0) {
578 resultDefer.resolve([]);
582 var siteLinks = hits.hits.map(function (siteLink) {
584 id: siteLink._source.id,
585 siteA: siteLink._source.siteA,
586 siteZ: siteLink._source.siteZ,
587 azimuthAz: siteLink._source.azimuthAZ,
588 azimuthZa: siteLink._source.azimuthZA,
589 length: siteLink._source.length,
590 type: siteLink._source.type
594 resultDefer.resolve(siteLinks);
595 }, resultDefer.reject);
597 return resultDefer.promise;
601 * Gets a promise with all details for a given site by its id.
602 * @param siteId {string} The id of the site to request the details for.
604 service.getSiteDetailsBySiteId = function (siteId) {
605 var resultDefer = $q.defer();
619 // get all site details
620 $mwtnDatabase.getFilteredData("mwtn", "site", 0, 1, siteQuery).then(
622 * Callback for the database request.
623 * @param result {{data: {hits: {hits: {_id: string, _index: string, _score: number, _source: {id: string, name: string, location: {lat: number, lon: number}, "amsl-ground": number, references: {"network-elements": string[], "site-links": string[]}}, _type: string}[], max_score: number, total: number}}, status: number}} The database result.
626 if (result.data.hits.total != 1) {
627 // todo: handle this error
628 resultDefer.reject("Error loading details for " + siteId + ((result.data.hits.total) ? ' Too many recoeds found.' : ' No record found.'));
630 var site = result.data.hits.hits[0];
633 name: site._source.name,
634 type: site._source.type,
636 lat: site._source.location.lat,
637 lng: site._source.location.lon
639 amslGround: site._source["amsl-ground"],
641 siteLinks: site._source.references["site-links"],
642 networkElements: site._source.references["network-elements"]
646 service.getSiteLinksByIds(siteDetails.references.siteLinks).then(
647 /** Callback for the database request.
648 * @param result {{id: string, siteA: string, siteZ: string }[]}
651 siteDetails.siteLinks = result;
652 resultDefer.resolve(siteDetails);
655 service.getPlannedNetworkElementsByIds(siteDetails.references.networkElements).then(
656 /** Callback for the database request.
657 * @param result {{id: string, name: string, type: string }[]}
660 siteDetails.plannedNetworkElements = result;
661 resultDefer.resolve(siteDetails);
665 return resultDefer.promise;
669 * Gets a promise with all details for a given link by its id.
670 * @param siteId {string} The id of the link to request the details for.
672 service.getLinkDetailsByLinkId = function (linkId) {
673 var resultDefer = $q.defer();
687 // get all site details
688 $mwtnDatabase.getFilteredData("mwtn", "site-link", 0, 1, linkQuery).then(
690 * Callback for the database request.
691 * @param result { DbLinkResult } The database result.
694 if (result.data.hits.total != 1) {
695 // todo: handle this error
696 resultDefer.reject("Error loading details for " + siteId + ((result.data.hits.total) ? ' Too many recoeds found.' : ' No record found.'));
698 var link = result.data.hits.hits[0];
701 siteA: link._source.siteA,
702 siteZ: link._source.siteZ,
703 siteNameA: link._source.siteNameA,
704 siteNameZ: link._source.siteNameZ,
705 length: link._source.length,
706 azimuthA: link._source.azimuthAZ,
707 azimuthB: link._source.azimuthZA,
708 airInterfaceLinks: link._source.airInterfaceLinks,
709 type: link._source.type,
710 airInterfaceLinks: link._source.airInterfaceLinks
713 service.getSitesByIds([link._source.siteA, link._source.siteZ]).then(
714 /** Callback for the database request.
715 * @param result {{ total: number, sites: { id: string, name: string } []}}
718 var siteA = result.sites.find(function (site) { return site.id == linkDetails.siteA });
719 var siteZ = result.sites.find(function (site) { return site.id == linkDetails.siteZ });
720 if (result.total != 2 || !siteA || !siteZ) {
721 resultDefer.reject("Could not load Sites for link "+linkDetails.id);
723 linkDetails.siteA = siteA;
724 linkDetails.siteZ = siteZ;
726 resultDefer.resolve(linkDetails);
730 return resultDefer.promise;
734 * Gets a promise which is resolved with an array of links using the given filter expression.
736 service.getLinks = function (sortColumn, sortDirection, filters, chunkSize, chunkSiteStartIndex) {
737 var resultDefer = $q.defer();
739 // determine the sort parameter
741 if (sortColumn != null && sortDirection != null) {
743 switch (sortColumn) {
746 order: sortDirection === 'desc' ? 'desc' : 'asc'
751 order: sortDirection === 'desc' ? 'desc' : 'asc'
756 order: sortDirection === 'desc' ? 'desc' : 'asc'
762 // determine the query parameter
764 if (filters == null || filters.length == 0) {
765 query["match_all"] = {};
768 filters.forEach(function (filter) {
769 if (filter && filter.field) {
770 switch (filter.field) {
772 regexp['siteIdA'] = '.*' + filter.term + '.*';
775 regexp['siteZ'] = '.*' + filter.term + '.*';
778 regexp[filter.field] = '.*' + filter.term + '.*';
783 query["regexp"] = regexp;
788 $mwtnDatabase.getFilteredSortedData("mwtn", "site-link", chunkSiteStartIndex, chunkSize, sort, query).then(processResult, resultDefer.reject);
790 $mwtnDatabase.getFilteredData("mwtn", "site-link", chunkSiteStartIndex, chunkSize, query).then(processResult, resultDefer.reject);
794 * Callback for the database request.
795 * @param result {{data: {hits: {hits: {_id: string, _index: string, _score: number, _source: {id: string, siteA: string, siteZ: string}, _type: string}[], max_score: number, total: number}}, status: number}} The database result.
797 function processResult (result) {
798 var hits = result && result.data && result.data.hits;
800 resultDefer.reject("Invalid result.");
804 var total = hits.total;
807 var siteLinks = hits.hits.map(function (siteLink) {
809 id: siteLink._source.id,
810 siteA: siteLink._source.siteA,
811 siteZ: siteLink._source.siteZ,
812 type: siteLink._source.type,
816 resultDefer.resolve({
822 return resultDefer.promise;
828 * Determines if a coordinate is in a bounding box
829 * @param bounds {{ top: number, left: number, right: number, bottom: number}} The bounding box.
830 * @param coordinate {{ lat: number, lng: number }} The coordinate.
831 * @return if the bounding box contains the coordinate
833 service.isInBounds = function(bounds, coordinate) {
834 var isLongInRange = (bounds.right < bounds.left)
835 ? coordinate.lng >= bounds.left || coordinate.lng <= bounds.right
836 : coordinate.lng >= bounds.left && coordinate.lng <= bounds.right ;
838 return coordinate.lat >= bounds.bottom && coordinate.lat <= bounds.top && isLongInRange;
841 service.getAllEdges = function () {
843 return getGenericChunk("edge", edges).then(function () {
849 * Retrieves all nodes.
850 * @return a Promis containing an array of nodes
852 service.getAllNodes = function () {
853 var resultDefer = $q.defer();
855 getGenericChunk("node", nodes).then(function () {
857 // recreate the tree structure from the flat list
858 var finalNodes = nodes.reduce(
859 /** @param acc { Node[] } */
860 function (acc, cur, ind, arr) {
861 // the site will be added with the first device, so it can not be missing after the first device is in
862 if (!acc.some(node => node.data.type == "site" && node.data.id == cur.data.grandparent)) {
866 id: cur.data.grandparent,
867 label: cur.data.grandparent,
868 active: cur.data.active
872 if (!acc.some(node => node.data.type == "device" && node.data.id == cur.data.parent)) {
875 _parent: cur.data.grandparent,
878 get parent() { return cur.data.grandparent },
879 set parent(val) { debugger; },
880 label: cur.data.parent,
881 active: cur.data.active
886 data: Object.keys(cur.data).reduce(function (obj, key) {
887 if (key == 'grandparent') {
890 cur.data.hasOwnProperty(key) && (obj[key] = cur.data[key]);
894 position: cur.position
899 resultDefer.resolve(finalNodes);
902 resultDefer.reject(err);
904 return resultDefer.promise;
907 /** @param nodes {{id: string,position:{ x:number, y:number}}}[]} */
908 service.saveChangedNodes = function (nodes) {
909 return $mwtnDatabase.getBase('topology').then(function (base) {
910 var resultDefer = $q.when();
912 nodes.forEach(function (node) {
913 resultDefer = resultDefer.then(function () {
914 return $mwtnDatabase.genericRequest({
919 command: encodeURI(node.id) +'/_update',
922 "position": node.position
932 // Start to initialize google maps api and save the returned promise.
933 service.googleMapsApiPromise = initializeGoogleMapsApi();
937 // private helper functions of $mwtnTopology
939 function getGenericChunk(docType, target) {
941 return $mwtnDatabase.getAllData('topology', docType, target.length || 0, size, undefined)
942 .then(function (result) {
943 if (result.status === 200 && result.data) {
944 var total = (result.data.hits && result.data.hits.total) || 0;
945 var hits = (total && result.data.hits.hits) || [];
946 hits.forEach(function (hit) {
947 target.push(hit._source || {});
949 if (total > target.length) {
950 return getGenericChunk(docType, target);
952 return $q.resolve(true);
954 return $q.reject("Could not load " + docType + ".");
957 resultDefer.reject(err);
960 return resultDefer.promise;
965 * Gets a promise which is resolved if the google maps api initialization is completed.
966 * @returns {promise} The promise which is resolved if the initialization is completed.
968 function initializeGoogleMapsApi() {
969 var googleMapsApiDefer = $q.defer();
970 window.googleMapsApiLoadedEvent = new Event("googleMapsApiLoaded");
972 window.addEventListener("googleMapsApiLoaded", function (event) {
975 * Calculates the bounds this map would display at a given zoom level.
977 * @member google.maps.Map
979 * @param {Number} zoom Zoom level to use for calculation.
980 * @param {google.maps.LatLng} [center] May be set to specify a different center than the current map center.
981 * @param {google.maps.Projection} [projection] May be set to use a different projection than that returned by this.getProjection().
982 * @param {Element} [div] May be set to specify a different map viewport than this.getDiv() (only used to get dimensions).
983 * @return {google.maps.LatLngBounds} the calculated bounds.
986 * var bounds = map.boundsAt(5); // same as map.boundsAt(5, map.getCenter(), map.getProjection(), map.getDiv());
988 google.maps.Map.prototype.boundsAt = function (zoom, center, projection, div) {
989 var p = projection || this.getProjection();
990 if (!p) return undefined;
991 var d = $(div || this.getDiv());
992 var zf = Math.pow(2, zoom) * 2;
993 var dw = d.getStyle('width').toInt() / zf;
994 var dh = d.getStyle('height').toInt() / zf;
995 var cpx = p.fromLatLngToPoint(center || this.getCenter());
996 return new google.maps.LatLngBounds(
997 p.fromPointToLatLng(new google.maps.Point(cpx.x - dw, cpx.y + dh)),
998 p.fromPointToLatLng(new google.maps.Point(cpx.x + dw, cpx.y - dh)));
1001 googleMapsApiDefer.resolve();
1004 var head = document.getElementsByTagName('head')[0];
1006 var callbackScript = document.createElement("script");
1007 callbackScript.appendChild(document.createTextNode("function googleMapsApiLoadedCallback() { window.dispatchEvent(window.googleMapsApiLoadedEvent); };"));
1009 var googleScript = document.createElement('script');
1010 googleScript.src = "https://maps.googleapis.com/maps/api/js?key=AIzaSyBWyNNhRUhXxQpvR7i-Roh23PaWqi-kNdQ&callback=googleMapsApiLoadedCallback";
1012 head.appendChild(callbackScript);
1013 head.appendChild(googleScript);
1015 return googleMapsApiDefer.promise;