Multiple KML w/ Google Earth Plugin/API Limitation? - google-earth

So, I'm building a web application using the Google Earth Plugin and API. However, I'm running into an issue with trying to display more than one KML file; only the last file given loads up. I'm using the process KmlNetworkLink to display everything.
The desktop application allows this, so I'm not sure if this is just a limitation on the API or not. Does anyone know if this is a limitation?
Thanks in advance.
The documentation I am looking at:
https://developers.google.com/earth/documentation/kml

EDIT: OP here. After working on this project for a few weeks, I've learned how to properly set up multiple KML tracks with the Google Earth plugin. I've revised my earlier answer (now shown below) to include a much cleaner and abstracted version of code. Hope this helps someone. Also (not listed here), it is possible store all of your KML locations in a single JSON and loop through it while calling createView() if needed.
<!DOCTYPE html>
<html>
<head>
<title>Google Earth API Display</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style>
body, html {
margin: 0;
padding: 0;
height: 100%;
width: 100%;
}
#earthDisplay {
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<div id="earthDisplay"></div>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript">
/**
* ==================================================================
* Global Variables and Statements
* ==================================================================
*/
var ge;
google.load("earth", "1", {"other_params": "false"});
google.setOnLoadCallback(init);
/**
* ==================================================================
* Functions
* ==================================================================
*/
function init()
{
google.earth.createInstance('earthDisplay', initCB, failureCB);
}
function initCB(instance)
{
var kmlLocation = "insert/your/file/here.kml";
ge = instance;
ge.getWindow().setVisibility(true);
createView(kmlLocation); // This function can be called multiple times to load different views.
}
function createView(kmlLocation)
{
var href = kmlLocation;
var link = ge.createLink('');
var networkLink = ge.createNetworkLink('');
link.setHref(href);
networkLink.set(link, true, true); // Sets the link, refreshVisibility, and flyToView
ge.getWindow().setVisibility(true);
ge.getFeatures().appendChild(networkLink);
}
function failureCB(errorCode)
{
alert("There has been an error with the Google Earth API. Please check your console.");
console.log("Error with Google Earth API: " + errorCode);
}
</script>
</body>
</html>

Related

How to used deckgl in "Bing Map"

We succeeded in displaying nearly 10 million pieces of data on a map using the Google Map API and Deck GL library.
However, I don't like the price and license policy of the Google Map API, so I'm going to change it to Bing Map.
It is difficult to find examples or examples of using Bing Map and Deckgl together on the Internet.
I understand that Deckgl can be used interworking with any base map if it meets a specific condition, but I'm not sure what the specific condition is.
What I want to know is as follows.
Can I use it with deckgl?
Which map do you prefer if you use Deckgl between OSM and BingMap?
Can you handle the map on BingMap? (getCenter, setCenter, etc.)
Is there a Map Event? (Clicked Event, Dragged Event, etc.)
Is 3D building or setTilt possible like MapBox?
There is no example for this currently, but this could be achieved by adding a canvas to the map as a custom layer. Here is an example: https://bingmapsv8samples.azurewebsites.net/#Canvas%20Layer
Alternatively, consider using Azure Maps. The Azure Maps web SDK wraps MapLibre (open source community fork of Mapbox), Deck.gl was originally writen for Mapbox, so it's not too difficult to access the underlying API in the Azure Maps web SDK and get deck.gl working. I've experimented with this a bit in the past. Here is a quick example:
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="IE=Edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<!-- Add references to the Azure Maps Map control JavaScript and CSS files. -->
<link rel="stylesheet" href="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.min.css" type="text/css" />
<script src="https://atlas.microsoft.com/sdk/javascript/mapcontrol/2/atlas.js"></script>
<script src="https://unpkg.com/deck.gl#8.4.9/dist.min.js"></script>
<script type='text/javascript'>
//https://blog.mapbox.com/coloring-lidar-4522ca5a7186
var map;
var arcData, currentStyle;
function GetMap() {
//Initialize a map instance.
map = new atlas.Map('myMap', {
center: [-85, 35],
zoom: 5,
pitch: 30,
view: 'Auto',
style:'grayscale_dark',
//Add your Azure Maps subscription key to the map SDK. Get an Azure Maps key at https://azure.com/maps
authOptions: {
authType: 'subscriptionKey',
subscriptionKey: '<Your Azure Maps Key>'
}
});
//Wait until the map resources are ready.
map.events.add('ready', function () {
//Load external data.
fetch('https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/arc/counties.json')
.then(response => response.json())
.then(({ features }) => {
//Add deck gl layer to map.
map.layers.add(new AzureMapsLayer({
id: 'arc',
data: calculateArcs(features),
getSourcePosition: d => d.source,
getTargetPosition: d => d.target,
getSourceColor: [255, 0, 0],
getTargetColor: [0, 255, 0],
getWidth: 2,
type: deck.ArcLayer
}));
});
});
}
function calculateArcs(data, selectedCounty) {
if (!data || !data.length) {
return null;
}
if (!selectedCounty) {
selectedCounty = data.find(f => f.properties.name === 'New York, NY');
}
const { flows, centroid } = selectedCounty.properties;
const arcs = Object.keys(flows).map(toId => {
const f = data[toId];
return {
source: centroid,
target: f.properties.centroid,
value: flows[toId]
};
});
const scale = Math.random();
arcs.forEach(a => {
a.gain = Math.sign(a.value);
a.quantile = scale;
});
return arcs;
}
class AzureMapsLayer extends atlas.layer.Layer {
constructor(options) {
super(options.id);
this._mbLayer = new deck.MapboxLayer(options);
this.source = new atlas.source.DataSource();
}
/**
* Internal method for building the mapbox layers.
* Because this layer only wraps others this is always empty.
* #internal
*/
_buildLayers() {
return [this._mbLayer];
}
/**
* Internal method for getting the ids of the mapbox layers this layer produces.
* Because this layer wraps others we return their ids.
* #internal
*/
_getLayerIds() {
return [this.id];
}
_getSource() {
return this._mbLayer.source;
}
/**
* #internal
*/
_getSourceIds() {
var ids = new Set();
ids.add(this.source.getId());
return ids;
}
}
</script>
<style>
html, body, #myMap {
height: 100%;
width: 100%;
padding: 0;
margin: 0;
}
</style>
</head>
<body onload="GetMap()">
<div id="myMap"></div>
</body>
</html>
Here is what the above code generates:

ArcGIS JavaScript API Popup Not Referencing REST Service Layer

The content in the popup created through the variable "popupCustom" is displaying string instead of referencing the specified field {IN_COUNTRY}. I followed the ArcGIS JS API Popup Tutorials, & can't see what my error is in failing to grab the attributes associated with that field. Here's the code -- any help is greatly appreciated!
*note: feature layer url within "Cyber_Areas" variable points to REST URL for referenced Feature Class.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<title>Search widget with multiple sources - 4.6</title>
<style>
html,
body,
#viewDiv {
padding: 0;
margin: 0;
height: 100%;
width: 100%;
}
</style>
<link rel="stylesheet" href="https://js.arcgis.com/4.6/esri/css/main.css">
<script src="https://js.arcgis.com/4.6/"></script>
<script>
require([
"esri/Map",
"esri/views/MapView",
"esri/widgets/BasemapToggle",
"esri/widgets/Legend",
"esri/layers/TileLayer",
"esri/layers/FeatureLayer",
"esri/widgets/Search",
"esri/widgets/LayerList",
"esri/PopupTemplate",
"dojo/on",
"dojo/domReady!"
], function(
Map,
MapView,
BasemapToggle,
Legend,
TileLayer,
FeatureLayer,
Search,
LayerList,
PopupTemplate,
on
) {
var Cyber_Areas = new FeatureLayer({
url: "*inserturl*",
outFields: ["IN_COUNTRY"],
popupTemplate: popupCustom
});
var map = new Map({
basemap: "osm"
});
map.add(Cyber_Areas);
var view = new MapView({
container: "viewDiv",
map: map,
center: [-87.172865, 34.077613], // lon, lat
zoom: 16
});
var searchWidget = new Search({
view: view,
popupOpenOnSelect: false
});
view.ui.add(searchWidget, {
position: "top-left",
index: 0
});
var popupCustom = searchWidget.on('select-result', function(evt){
//console.info(evt);
view.popup.open({
location: evt.result.feature.geometry, // location of the click on the view
title: "Service Availability:", // title displayed in the popup
content: "<p><b>{IN_COUNTRY}"
});
});
});
</script>
</head>
<body>
<div id="viewDiv"></div>
</body>
</html>
From your code you are mixing the popup template value with when to display it. And those are two different things.
First, you are not setting correctly the popup template of the layer. It should be a PopupTemplate.
It seems to me that in you code the layer definition should be something like this,
var Cyber_Areas = new FeatureLayer({
url: "*inserturl*",
popupTemplate: {
outFields: ["IN_COUNTRY"],
title: "Service Availability:",
content: "<p><b>{IN_COUNTRY}</b></p>"
}
});
Now if you don't want the default behavior of the popup (left click on a feature), you cant disable it like this,
view.popup.autoOpenEnabled = false; // <- disable view popup auto open
And then you can open it wherever you want like this,
view.popup.open({ // <- open popup
location: evt.result.feature.geometry, // <- use map point of the event result
fetchFeatures: true // <- fetch the selected features (if any)
});
You have to understand that the fields you use in the content of the popup template are related to the layer. That is why i set in the popup of the view to fetch the results.

Polymer REST backend

I have a backend written in golang exposing /api/list interface. It returns lists when called from GET and create new list when it receive POST with parameters.
I can read it with standard core-ajax element, there is a huge amount of examples to do that.
What I didn't understood is what should I do, when I want to create new element through POST? I read the documentation and searched for sample code for half day, can you point me to right direction?
//
Ok, thanks for help, it was really only bad format of json I was sending. There is still dark cloud in my mind telling that I misunderstood something from conceptual view. Is this:
<link rel="import" href="bower_components/polymer/polymer.html">
<link rel="import" href="bower_components/core-ajax/core-ajax.html">
<polymer-element name="channels-service" attributes="channels">
<template>
<style>
:host {
display: none;
}
</style>
<core-ajax id="ch_load"
auto
url="/api/list"
on-core-response="{{channelsLoaded}}"
handleAs="json">
</core-ajax>
<core-ajax id="ch_update"
url="/api/list"
on-core-response="{{channelsUpdated}}"
method="POST"
handleAs="json">
</core-ajax>
</template>
<script>
Polymer('channels-service', {
created: function() {
this.channels = [];
},
channelsLoaded: function() {
// Make a copy of the loaded data
this.channels = this.$.ch_load.response.slice(0);
},
newChannel: function(ch_name) {
// this.$.ch_update.body = "ch_name";
this.$.ch_update.body = '{"Name":"pitchalist2"}'
this.$.ch_update.go();
},
channelsUpdated: function() {
//window.log(this.$.ch_update.response.slice(0));
}
});
</script>
</polymer-element>
correctly written data layer? It looks very counterintuitive to me and in examples using local data storage it works way easier.
You can send a POST request by setting the method attribute (method="POST") and the body attribute (body='{"my":"data"}'). Indeed you need a second iron-ajax element for this request.
See the attributes section in the iron-ajax documentation.

Interval role i-lines are not displayed in Google line chart

I am trying to add min and max limits to a Google chart, which I generate using a Perl script from CSV data - by using the interval role for these 2 values.
Unfortunately the I-lines are not displayed at my line chart, even though I've set the min and max limits to the -100 and 100 for the sake of testing.
Only the main data is being displayed:
Can anybody please spot the error, what is wrong with my very simple test case?
Please just save the code below as an HTML-file and open in a browser:
<!DOCTYPE HTML>
<html>
<head>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script type="text/javascript" src="https://www.google.com/jsapi?autoload={'modules':[{'name':'visualization','version':'1','packages':['corechart']}]}"></script>
<script type="text/javascript">
var data = {"L_B8_ACLR_50_0_QPSK_1_H":{"rows":[
{"c":[{"v":"UTRA_1_DOWN"},{"v":-100},{"v":100},{"v":"-42.46912"}]},
{"c":[{"v":"E-UTRA_1_DOWN"},{"v":-100},{"v":100},{"v":"-39.9545"}]},
{"c":[{"v":"E-UTRA_1_UP"},{"v":-100},{"v":100},{"v":"-48.68408"}]},
{"c":[{"v":"UTRA_1_UP"},{"v":-100},{"v":100},{"v":"-49.45148"}]},
{"c":[{"v":"UTRA_2_UP"},{"v":-100},{"v":100},{"v":"-58.96674"}]}],
"cols":[
{"p":{"role":"domain"},"label":"MEASUREMENT","type":"string"},
{"p":{"role":"interval"},"label":"LSL","type":"number"},
{"p":{"role":"interval"},"label":"USL","type":"number"},
{"p":{"role":"data"},"label":"1142926087","type":"number"}]}};
function drawCharts() {
for (var csv in data) {
var x = new google.visualization.DataTable(data[csv]);
var options = {
title: csv,
width: 800,
height: 600
};
var chart = new google.visualization.LineChart(document.getElementById(csv));
chart.draw(x, options);
}
}
$(function() {
google.setOnLoadCallback(drawCharts);
});
</script>
</head>
<body>
<div id="L_B8_ACLR_50_0_QPSK_1_H"></div>
</body>
</html>
(I don't want to use methods like addColumn or addRows. Instead I prepare my data as data structure in my Perl script and then JSON-encode and pass it to DataTable ctr).
You must specify the interval-role column after the data-column. As written in the API :
"All columns except domain columns apply to the nearest left neighbor to which it can be applied"
So if you change the order (and here with some smaller intervals)
var data = {"L_B8_ACLR_50_0_QPSK_1_H":{"rows":[
{"c":[{"v":"UTRA_1_DOWN"},{"v":"-42.46912"},{"v":-50},{"v":-45}]},
{"c":[{"v":"E-UTRA_1_DOWN"},{"v":"-39.9545"},{"v":-50},{"v":-45}]},
{"c":[{"v":"E-UTRA_1_UP"},{"v":"-48.68408"},{"v":-50},{"v":-45}]},
{"c":[{"v":"UTRA_1_UP"},{"v":"-49.45148"},{"v":-50},{"v":-45}]},
{"c":[{"v":"UTRA_2_UP"},{"v":"-58.96674"},{"v":-50},{"v":-45}]}],
"cols":[
{"p":{"role":"domain"},"label":"MEASUREMENT","type":"string"},
{"p":{"role":"data"},"label":"1142926087","type":"number"},
{"p":{"role":"interval"},"label":"LSL","type":"number"},
{"p":{"role":"interval"},"label":"USL","type":"number"}
]}};
..You end up with :

How to use hammer.js with Angular.js

I'm new with Angular.js and reading i know angular dont have events like tap, double tap, etc. I'm trying to combine with Hammer.js without success.
Code from gist
/**
* angular-hammer.js
* Inspired by AngularJS' implementation of "click dblclick mousedown..."
*
* This ties in the Hammer events to attributes like:
*
* hm-tap="add_something()"
* hm-swipe="remove_something()"
*
* and also has support for Hammer options with:
*
* hm-tap-opts="{hold: false}"
*
* or any other of the "hm-event" listed underneath.
*/
angular.forEach('hmTap:tap hmDoubletap:doubletap hmHold:hold hmTransformstart:transformstart hmTransform:transform hmTransforend:transformend hmDragstart:dragstart hmDrag:drag hmDragend:dragend hmSwipe:swipe hmRelease:release'.split(' '), function(name) {
var directive = name.split(':');
var directiveName = directive[0];
var eventName = directive[1];
angular.module('MYMODULE').directive(directiveName,
['$parse', function($parse) {
return function(scope, element, attr) {
var fn = $parse(attr[directiveName]);
var opts = $parse(attr[directiveName + 'Opts'])(scope, {});
element.hammer(opts).bind(eventName, function(event) {
scope.$apply(function() {
console.log("Doing stuff", event);
fn(scope, {$event: event});
});
});
};
}]);
});
now i've added to my html file
<!--...-->
<script src="/js/jquery-1.9.0.js"></script>
<script src="/js/angular.js"></script>
<script src="/js/hammer.js"></script>
<script src="/js/jquery.hammer.js"></script>
<script src="/js/angular-hammer.js"></script>
<script src="/js/app/controllers.js"></script>
</html>
But when i try to use in my html it doesn't work.
I've tried with
<li hmTap="click($event)">...</li>
<li ngTap="click($event)">...</li>
<li hm-tap="click($event)">...</li>
<li ng-tap="click($event)">...</li>
no one works, can help me with this trouble?
Give this a go.
Add hammer.js and jquery.hammer.js to your solution. With JQuery and Angular.
Something like this:
<script src="js/jquery/jquery.min.js"></script>
<script src="js/hammer/hammer.js"></script>
<script src="js/hammer/jquery.hammer.js"></script>
<script src="js/angular/angular.min.js"></script>
Then use Angular directives to define new HTML attributes.
In this example I am creating a new attribute called on-tap
angular.module('myApp.directives', [])
.directive('onTap', function () {
return function (scope, element, attrs) {
return $(element).hammer({
prevent_default: false,
drag_vertical: false
})
.bind("tap", function (ev) {
return scope.$apply(attrs['onTap']);
});
};
});
You can then create a directive per Hammer event:
hold, tap, doubletap, transformstart, transform, transformend, dragstart, drag, dragend, release, swipe.
Your HTML would then look like this:
<button on-tap="DoAngularMethod()">Tap me</button>
Here is exactly what are you looking for:
https://github.com/wzr1337/angular-gestures
this new libary allows you to implement all of gestures you need.
also read this:
http://www.ng-newsletter.com/posts/angular-on-mobile.html
Hope you find it interesting.
:)
There are some hammer directive implementations for Angular you could try to use:
https://github.com/randallb/angular-hammer
https://github.com/dreame4/angular-hammer
I used randallb lib and i added
'hmPan:pan',
'hmPanLeft:panleft',
'hmPanRight:panright',
in the hmGestures array, so i can use pan too!