POST
|
works like a charm! For everyone who woul like to copy the URL params as the folks just posted images: autoplay=1&loop=1&controls=0 And no, it works without the & syntax 😉
... View more
10-11-2018
01:10 AM
|
0
|
0
|
1608
|
POST
|
Unfortunately I wasn't able to alter the Referer item (translated to English): "Attempt to use a forbidden header was rejected: Referer"
... View more
09-25-2018
05:57 AM
|
0
|
0
|
1258
|
POST
|
I've created a small toolbox where I use a GPString input with a filter list: param1 = arcpy.Parameter(
displayName="OSM tag value",
name="in_key",
datatype="GPString",
parameterType="Required",
direction="Input",
multiValue=True
)
param1.filter.list = self.getConfig("tags")
the function getConfig reads a JSON and fills the values of the String input. Yet I want to enable the user to use an own string for the input at param1. Is there any chance to do this with the given approach or do I need to create a second input for a user defined GPString? Here is the issue on GitHub: allow manual input of tags and keys · Issue #16 · riccardoklinger/OSMquery · GitHub
... View more
08-30-2018
02:39 AM
|
0
|
0
|
818
|
BLOG
|
There are multiple ways how you might use OpenStreetMap. The most commonly used might be the OSM dataset as a basemap. Some others might already used the ArcGIS Editor for OSM. But I haven't found a soultion to query OSM directly from within ArcGIS. The question to answer was: "Where are bakeries in Northumberland?" The Overpass API offers a great query tool to define queries and get the result on a map. I wanted to have a comparable but easy to use tool in ArcGIS so I created OSMQuery. You can use OSMQuery for free. The Inputs of OSMQuery OSMQuery takes only a simple set of inputs at the moment: - a tag like "building", "shop" or "amenity" - a key like "farm", "bakery" or "place_of_worship" - an area which will be used as a "related" feature - or a bounding box / spatial extent The tool uses the name of the region and gets an area ID from the Nominatim Geocoder. This area ID or the bounding box of the spatial extent will be used to create a query for the Overpass API. (the above image is already outdated after one day. See the end of this post!) The query is send to the Overpass API which responds with a JSON object. The JSON object contains elements like nodes, ways and relations. The Logic of OSMQuery The processing logic is splitted in two parts. First I needed to determine whether points, line and/or polygon feature classes will be needed to store the features. This is a bit tricky as polygons are also "ways" in terms of OpenStreetMap logic. The response of a way not only contains the tags of a way but also the id's of nodes defining the way geometry. A polygon can be created if the first nodeID in the list of nodes is also the last nodeID for the way: As you can see, the result way has 71 nodes and the response looks like this: {
"version": 0.6,
"generator": "Overpass API 0.7.55.4 3079d8ea",
"osm3s": {
"timestamp_osm_base": "2018-08-29T08:10:02Z",
"copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL."
},
"elements": [
{
"type": "node",
"id": 2496059820,
"lat": 51.0703208,
"lon": 4.9863343
},
{
"type": "node",
"id": 2496059823,
"lat": 51.0703401,
"lon": 4.9865653
},
...
{
"type": "node",
"id": 2496059818,
"lat": 51.0702779,
"lon": 4.9863413
},
{
"type": "way",
"id": 242035363,
"nodes": [
2496059820,
2496059823,
...
2496059818,
2496059820
],
"tags": {
"OnroerendErfgoed:criteria": "M",
"addr:city": "Veerle",
"amenity": "place_of_worship",
"building": "house",
"denomination": "roman_catholic",
"description": "Parochiekerk",
"heritage": "4",
"heritage:operator": "OnroerendErfgoed",
"heritage:website": "https://inventaris.onroerenderfgoed.be/dibe/relict/41142",
"image": "https://commons.wikimedia.org/wiki/File:Veerle_-_Onze-Lieve-Vrouw-in-de-Wijngaardkerk.jpg",
"name": "Onze-Lieve-Vrouw-in-de-Wijngaardkerk",
"ref:OnroerendErfgoed": "41142",
"religion": "christian",
"source": "AGIV",
"wikimedia_commons": "Category:OLV in de Wijngaardkerk (Veerle)"
}
}
]
} Furthermore the result has not a defined datamodel. Each node may come with a very different set of tags and keys. So I needed to create a union of tags for each feature class type. The adding fields algorithm is the slowest part in my toolbox as the number of tgas can be as high as 40... After I created the feature classes and added the fields accrodiung the list of attributes/tags I can add the features from the Overpass repsonse. This is quite easy in the end but it is a pain for the polylines and polygons as I needed to read the lat/lon attributes for each node recursively. But in the end: There are 33 bakeries in Northumberland, UK according to the OpenStreetMap dataset: Here is the compared dataset in the Overpass API: If you want to test/download/develop the toolbo, go ahead and use the OSMQuery repo at GitHub. Here is a short video about the usage: Thanks to the support of GitHub user rastrau OSMQuery also supports multiple queries now:
... View more
08-29-2018
01:32 AM
|
5
|
0
|
3051
|
BLOG
|
Sometimes you see this great tile layer in a web map and you want to use it in your ArcGIS online project or ArcGIS Pro. That was the feeling I had, seeing this nice little BVG basemap. The usage of this kind-of-twisted basemap was a bit tricky as the logic for latitude-longitude to tile numbers was a bit twisted. In products like ArcGIS Online/ ArcGIS Enterprise there is no way to alter the logic of this. So if you want to use a tile layer like this in AGOL you need to intervene somewhere else. Interrupt the Tile Acquisition The tiles are fetched from a server any time you pan or zoom the map. The approach I used to get the tiles uses a simple php proxy. So instead of collecting the tiles from the webserver via a direct pull, we use the php script which gets our request, calculates the somewhat twisted tile number for the row and requests the correct tile and forwards it to your AGOL project. But let us start with a simple trick which serves as the basis: kitten watermarks. PlaceKitten serves placeholder images in a very common format: http://placekitten.com/256/256?image=5 where the first "256" is the width, the second one the height and the last one the image you would like to see (image number 5). The images are served as PNGs. As said, we will use a php script to fetch the data and send every image back to AGOL as we receive it. This makes no sense at all from a geo perspective. So you will need a php-enabled server. I was using a Windows virtual machine with IIS and installed PHP by using the Web Platform Installer. So we need a way to fetch images from the web using php. I stumbled upon the cURL method you might already know from the command line world of Linux based systems. All we need to do is to mimic our script to look like a real computer trying to get an image from this server: <?php
header ('Content-Type: image/png'); #content returns as an image
$url = 'https://placekitten.com/256/256?image='; #this is the base url
$ynew = rand(1,16); #we can collect one out of 16 images
$url .= $ynew;
$ch = curl_init(); #Initialize a cURL session
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); #don't verify the peer's SSL certificate
curl_setopt($ch, CURLOPT_FRESH_CONNECT, TRUE); #don't use any cache
curl_setopt($ch,CURLOPT_URL, $url); #provide the URL to use in the request
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1); #return the transfer as a string of the return value of curl_exec() instead of outputting it directly.
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.A.B.C Safari/525.13"); #set HTTP user-agent header
$data = curl_exec($ch); #perform the cURL session
curl_close($ch); #Close the cURL session
echo $data; #return the date (image)
?> Now you can already use the real URL of your server (don't even try localhost my friend 😉 ) and embed the placeholder cats in your AGOL project. Open up a new web map and add a new web based layer: I am using here the proposed "{level}/{col}/{row}.png" connotation as we would like to create new calls for every image to get a true random pattern: Look, all those cute kitties... Running real Cartography At the very moment we do have an idea on how to forward images from the web into AGOL as a web layer. Now let's move on and fill these placeholder with "real cartography". As we have seen in this last post, the naming schema for the BVG map is a bit crooked. But first, our script needs to know, which images it should pull. So we need to send the script some parameters. We will do this in a similar way like the real TMS servers: Send the ZXY parameters via the URL, read the in our script and append the URL with them: <?php
header ('Content-Type: image/png');
$y = $_GET['y']; #the parameter y is stored as a string in the variable y
$x = $_GET['x']; #the parameter x is stor...
$z = $_GET['z']; #you get it, right?
$ynew =( -1*intval($y))+pow(2,intval($z))-1; #recalculate the y coordinate using the found formula.
$url = 'https://fahrinfo.bvg.de/tiles/base/'.$z.'/'.$x.'/'.$ynew.'.png'; #creating the url
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_FRESH_CONNECT, TRUE);
curl_setopt($ch,CURLOPT_URL, $url);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.A.B.C Safari/525.13");
$data = curl_exec($ch);
curl_close($ch);
echo $data;
?> The parameters are send to the PHP-enabled server using the following schema: http://your-server-URL.com/name-of-your-script.php?z={level}&x={col}&y={row} Using this script you will get a nice BVG basemap in your ArcGIS Online editor Map Viewer: Once you saved the web map you can even open it in ArcGIS Pro and use it for your purposes by adding the map from Portal: Multiple Servers If you want to handle multiple servers with the same script you can add a fourth parameter which handles the tileserver and adding some switches: <?php
header ('Content-Type: image/png');
$tileservice=null; #
if (isset($_GET['t'])){
$tileservice = $_GET['t'];
}
if(!$tileservice){
$tileservice = 'bvg'; #if no tileserver parameter was handled
}
$y = $_GET['y'];
$x = $_GET['x'];
$z = $_GET['z'];
$server = array();
switch ($tileservice){
case 'cat':
$url = 'https://placekitten.com/256/256?image=';
$ynew = rand(1,16);
$url .= $ynew;
#echo $url;
break;
case 'bvg':
default:
$ynew =( -1*intval($y))+pow(2,intval($z))-1;
$url = 'https://fahrinfo.bvg.de/tiles/base/'.$z.'/'.$x.'/'.$ynew.'.png';
break;
};
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_FRESH_CONNECT, TRUE);
curl_setopt($ch,CURLOPT_URL, $url);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.A.B.C Safari/525.13");
$data = curl_exec($ch);
curl_close($ch);
echo $data;
?> Now your proxy URL should look like this to get a cat tile: http://your-server-URL.com/name-of-your-script.php??t=cat&z=15&x=16371&y=10936 Sometimes you also want to have a fallback server if no tiles were fetched. Therefore we will check response of the cURL call and will use a black-white layer as a fallback layer: <?php
header ('Content-Type: image/png');
error_reporting(E_ALL);
ini_set('display_errors', '1');
$tileservice=null;
if (isset($_GET['t'])){
$tileservice = $_GET['t'];
}
if(!$tileservice){
$tileservice = 'bvg';
}
$y = $_GET['y'];
$x = $_GET['x'];
$z = $_GET['z'];
$server = array();
switch ($tileservice){
case 'cat':
#$server[] = 'https://placekitten.com/256/256';
$url = 'https://placekitten.com/256/256?image=';
$ynew = rand(1,16);
$url .= $ynew;
#echo $url;
break;
case 'bvg':
default:
$ynew =( -1*intval($y))+pow(2,intval($z))-1;
$url = 'https://fahrinfo.bvg.de/tiles/base/'.$z.'/'.$x.'/'.$ynew.'.png';
break;
};
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_FRESH_CONNECT, TRUE);
curl_setopt($ch,CURLOPT_URL, $url);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.A.B.C Safari/525.13");
$data = curl_exec($ch);
if (curl_getinfo( $ch, CURLINFO_HTTP_CODE ) != 200) {
curl_close($ch);
$server = array();
$server[] = 'http://a.tiles.wmflabs.org/bw-mapnik/';
$server[] = 'http://b.tiles.wmflabs.org/bw-mapnik/';
$server[] = 'http://c.tiles.wmflabs.org/bw-mapnik/';
$url = $server[array_rand($server)];
$url .= $z."/".$x."/".$y.".png";
#echo $url;
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_FRESH_CONNECT, TRUE);
curl_setopt($ch,CURLOPT_URL, $url);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.A.B.C Safari/525.13");
$data = curl_exec($ch);
}
curl_close($ch);
echo $data;
?> The map shows now both tiles: If you want to add some "annoying annotation" you might also add a nice watermark on those slippy tiles: <?php
header ('Content-Type: image/png');
error_reporting(E_ALL);
ini_set('display_errors', '1');
$tileservice=null;
if (isset($_GET['t'])){
$tileservice = $_GET['t'];
}
if(!$tileservice){
$tileservice = 'bvg';
}
$y = $_GET['y'];
$x = $_GET['x'];
$z = $_GET['z'];
$server = array();
switch ($tileservice){
case 'cat':
#$server[] = 'https://placekitten.com/256/256';
$url = 'https://placekitten.com/256/256?image=';
$ynew = rand(1,16);
$url .= $ynew;
#echo $url;
break;
case 'bvg':
default:
$ynew =( -1*intval($y))+pow(2,intval($z))-1;
$url = 'https://fahrinfo.bvg.de/tiles/base/'.$z.'/'.$x.'/'.$ynew.'.png';
break;
};
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_FRESH_CONNECT, TRUE);
curl_setopt($ch,CURLOPT_URL, $url);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.A.B.C Safari/525.13");
$data = curl_exec($ch);
$size = curl_getinfo($ch, CURLINFO_SIZE_DOWNLOAD );
if (curl_getinfo( $ch, CURLINFO_HTTP_CODE ) != 200) {
curl_close($ch);
$server = array();
$server[] = 'http://a.tiles.wmflabs.org/bw-mapnik/';
$server[] = 'http://b.tiles.wmflabs.org/bw-mapnik/';
$server[] = 'http://c.tiles.wmflabs.org/bw-mapnik/';
$url = $server[array_rand($server)];
$url .= $z."/".$x."/".$y.".png";
#echo $url;
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_FRESH_CONNECT, TRUE);
curl_setopt($ch,CURLOPT_URL, $url);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/0.A.B.C Safari/525.13");
$data = curl_exec($ch);
}
curl_close($ch);
$im = imagecreatefromstring($data);
$stamp = imagecreatetruecolor(100, 30);
imagestring($stamp, 10, 10, 5, 'watermark', 0xff0000);
// set borders and get sizes
$marge_right = 100;
$marge_bottom = 100;
$sx = imagesx($stamp);
$sy = imagesy($stamp);
// copy the watermark with transparency of 50 into the original image
imagecopymerge($im, $stamp, imagesx($im) - $sx - $marge_right, imagesy($im) - $sy - $marge_bottom, 0, 0, imagesx($stamp), imagesy($stamp), 50);
header('Content-Type: image/png');
imagepng($im);
imagedestroy($im);
?> But this will create some load on the server... so you might want to rethink this 😉 Final remarks Running PHP with a "bad" script can produce a very slow response of your proxied tile server. So you should try to cache tiles on your server. But this is a total different story.
... View more
08-19-2018
01:12 PM
|
3
|
0
|
2197
|
BLOG
|
I like to explore the city via public transport and as I am a big map fan, I fell in love with the map of our Berlin based public transport service provider BVG. The map is a great product and has so many levels of information. It has a well know style established in Berlin so I wanted to use it in my own web mapping application. As I scanned the website of the BVG I noticed the leaflet map on their page: So I asked the question: "Where does the tiles came from". A short look in the network developer pane in firefox revealed the address https://fahrinfo.bvg.de/tiles/base/15/17605/22019.png (example) The Tiles can be easily added inside a ArcGIS Javascript API based webmap: var tiledLayer = new WebTileLayer({
urlTemplate: "https://fahrinfo.bvg.de/tiles/base/{level}/{col}/{row}.png",
title: "BVG Basemap",
copyright:
"Map Data by <a href='https://Bvg.de'>BVG</a>, " +
"Imagery by <a href='https://Bvg.de/'>BVG</a>"
}); But as soon you embed this, you will notice: There are only 404s send back from the BVG server. So I examined the URL-schema and found out, that the Y id is somewhat different compared to mapbox or OSM Y ids for the same tile: y-tile: BVG y-tile: Mapbox zoomLevel 22020 10747 15 22015 10752 15 22013 10754 15 11007 5376 14 44025 21510 16 44026 21509 16 So the tile indices are somehow shifted compared to the normal schema... So let us reverse engineer the conversion. First approach: plot the IDs on a chart and try some regression: But as you can see: the line looks straight but the equation is just crap. Let's zoom in a bit: This is of course a bit different: the regression line flipped and we do have no residuals. The summand looks very common as it is near a power of 2. to be exact: 2^15-1 So there is a direct equation to transform the original tile row into a BVG tile row: var rownew = -1*row+Math.pow(2,level)-1; So now we need to import this logic into the method ArcGIS uses to get the tiles. And BAMM: here are the tiles: var tiledLayer = new WebTileLayer({
urlTemplate: "https://fahrinfo.bvg.de/tiles/base/{level}/{col}/{row}.png",
title: "BVG Basemap",
getTileUrl: function (level, row, col) {
var rownew = -1*row+Math.pow(2,level)-1;
return this.urlTemplate.replace("{level}", level).replace("{col}", col).replace("{row}", rownew);
},
copyright:
"Map Data by <a href='https://Bvg.de'>BVG</a>, " +
"Imagery by <a href='https://Bvg.de/'>BVG</a>"
}); Explore the webmap on CodePen, or download it below. And last but not least: You can even rotate it: (view in My Videos) In the end: If you know the math, you can change the world 😉
... View more
08-03-2018
05:22 AM
|
4
|
0
|
3802
|
POST
|
Unfortunately the changes made to the tool does not led to a valid tool in the target system. Any othe suggestions?
... View more
07-13-2018
05:54 AM
|
0
|
0
|
1249
|
POST
|
Hi Collin, Just checked this python toolbox back in ArcMAP 10.6 AND ArcGIS Pro 2.1.3 and 2.2. import arcpy
class Toolbox(object):
def __init__(self):
"""Define the toolbox (the name of the toolbox is the name of the
.pyt file)."""
self.label = "Toolbox"
self.alias = ""
# List of tool classes associated with this toolbox
self.tools = [Tool]
class Tool(object):
def __init__(self):
"""Define the tool (tool name is the name of the class)."""
self.label = "Tool"
self.description = ""
self.canRunInBackground = False
def getParameterInfo(self):
"""Define parameter definitions"""
point = arcpy.Parameter(
displayName= "buffer center",
name="buffer center",
datatype="GPFeatureRecordSetLayer",
parameterType="Required",
direction="Input")
params = [point]
return params
def isLicensed(self):
"""Set whether tool is licensed to execute."""
return True
def updateParameters(self, parameters):
"""Modify the values and properties of parameters before internal
validation is performed. This method is called whenever a parameter
has been changed."""
return
def updateMessages(self, parameters):
"""Modify the messages created by internal validation for each tool
parameter. This method is called after internal validation."""
return
def execute(self, parameters, messages):
import time;
"""The source code of the tool."""
def createDistances(incr,maximum):
#as we have the raster now, we will need a multiple ring buffer:
distances = []
for dist in range(incr,maximum+1,incr):
distances.append(str(dist))
distancesString = ";".join(distances)
return distancesString
distancesString = createDistances(100,1000)
i=0;
while i<10:
start = time.time()
arcpy.analysis.MultipleRingBuffer(parameters[0].value, "tester" + str(i), distancesString, "Meters", "distance", "ALL", "FULL")
end = time.time()
arcpy.AddMessage(end - start)
i+=1
return
The results show significant perfomance boost on my machine! The raw values in seconds are shown here: I can see a performance bioost of about 20x compared to ArcMAP and 50x compared to ArcGIS Pro 2.1.3 using ArcGIS Pro 2.2
... View more
07-12-2018
07:06 AM
|
3
|
1
|
2405
|
POST
|
I just saw recently, that my code contained a nice little snippet in the updateMessages function, which is "invalid" from a Python persepctive: parameters[8].setWarningMessage (
"Original value was " + str(old) + "m. "
"Rounded to " + parameters[8].valueAsText + " as it is a multiple of 1000m.") Yet the tool was valid.... I will try the changes in the target environment...
... View more
07-11-2018
12:57 AM
|
0
|
1
|
1249
|
POST
|
Guys, I do have a Python toolbox developed under ArcGIS 10.6. This is used in ArcGIS Desktop 10.3.1 and it works without any issues. Also the publishing on ArcGIS Server 10.3.1 works without any issues. When I open the toolbox on the server in another environment I see the GUI and can select start. Yet after the start it terminates with the message "tool is invalid". It works perfectly on my ArcGIS Server 10.3.1 single machine deployment but fails with the described error in the other environment. The python version is the same on both servers. I've seen this thread, yet the answers were not quite sufficient. The tool structure is as follows: Any ideas?
... View more
07-10-2018
06:19 AM
|
0
|
2
|
1415
|
BLOG
|
As I've presented a way to create your own widget for the Web Appbuilder in my last post I would like to explain the options for multi language support in your widget. NLS The strings for our last widgets are more or less hard coded in the Setting.html and Widget.html file at the moment. If you want to replace these string with the language set inside the browser or portal you need to label the strings and create a library of strings. This is done for the Setting.html and for the Widget.html at different places inside the widget structure. Setting.html The setting folder contains a folder called nls. Inside you create a folder for each language you would like to support and an initial strings.js file with the strings that should be used in the settings of the widget by default. The strings.js file consists of the actual default strings and an indicator, whether or not a single language (we only support "de" in our example) is supported: define({
root: ({
ZoomSetCheckboxText: "Show Zoom Level",
ScaleSetCheckboxText: "Show Scale",
configText: "Set Configuration:"
}),
"de": 1,
"es": 0
}); You might have noticed that we use a variable called "ZoomSetCheckboxText" and "ScaleSetCheckboxText" and define the content of the variable with some basic strings. These strings should be used, wherever the variables are used. We place the variables inside the Setting.html file. This looks now a little bit different as the nomenclature looks a bit weird: <div>
<div class="title">${nls.configText}</div>
<div class="row">
<input type="checkbox" id="ZoomSetCheckbox" data-dojo-attach-point="ZoomSetCheckbox"></input>
<p style="display:inline">${nls.ZoomSetCheckboxText}</p>
</div>
<div class="row">
<input type="checkbox" id="ScaleSetCheckbox" data-dojo-attach-point="ScaleSetCheckbox"></input>
<p style="display:inline">${nls.ScaleSetCheckboxText}</p>
</div>
</div> All strings are defined with a "${nls.XXX}" schema. This allows of the strings and the automatic translation according your definition in the strings.js file. Off course we also need to define a translation in German in identificationour case. This is done inside the "de" subfolder in the nls folder. We place a second strings.js there which holds the German strings: define({
"ZoomSetCheckboxText": "Zeige Zoomlevel",
"ScaleSetCheckboxText": "Zeige Maßstab",
"configText": "Einstellung",
}); Please not the different usage of string indicators compared to the default strings.js file in the parent folder. The Widget.html The widget itself works similar. Inside the Widget folder you create a nls folder with subfolders regarding the desired languages you would like to support. A list of languages which are supported, can be seen here: Once again we substitute the strings used in the original file with the "${nls.XXX}" schema: <div class="ZoomLeveLInfo">
<div data-dojo-attach-point="zoomInfoDiv">${nls.label1} <p style="display: inline;" data-dojo-attach-point="MapZoomInfo"></p></div>
<div data-dojo-attach-point="scaleInfoDiv">${nls.label2} <p style="display: inline;" data-dojo-attach-point="scaleInfo"></p></div>
</div> In our strings.js file we set the default strings: define({
root: ({
_widgetLabel: "Zoom Level Info"
label1: "Zoom Level:",
label2: "Approx. Scale:",
}),
"de": 1
}); In the "de" subfolder I use a similar pattern as describe in the chapter above: define({
"_widgetLabel": "Zoomstufeninfo",
"label1": "Zoom Stufe:",
"label2": "ungef. Maßstab:"
}); The Result The widget is now able to start in English as well as in German depending on the settings in the Portal:
... View more
06-14-2018
05:03 AM
|
1
|
0
|
903
|
BLOG
|
Working with a GIS results sometimes in quite advanced workflows, models and so on. The next step might be: How to get these tools shared so other parties and people can work with them? There are multiple approaches to do so: publish Geoprocessing as a Web Tool. ArcGIS JavaScript API Widgets for the Web AppBuilder In this blog post I would like to describe the last approach and create a customizable Widget for the Web AppBuilder. Web AppBuilder Dev Edition You might ask yourself, why do I need the Web AppBuilder Dev Edition (short: WAB)? As we will create a widget is it always good to test the solution prior publishing it as a custom application on AGOL. With the Web AppBuilder Dev Edition we can use template widgets and test the widgets local on a server (or a resource that has a pretty domain...) WAB runs on Node.js but is only available for Windows at the moment. Once it is unzipped and configured with your ArcGIS account (connect with Portal for ArcGIS, create an App on your Portal, register the App with your Portal) you're ready to go: Follow the steps defined at the Get started section. The Widgets Purpose For this introduction I would like to create a widget that shows the scale as well as the current zoom level of a map. Of course it is somewhat an enhancement of the standard Scalebar Widget. Therefore I will first show the standalone way and later on describe the enhancement of the default Scalebar widget to do the same: Show not only the Scalebar but also show the zoom level as well as the scale of the underlying map. A Widget's Structure A Widget itself consists of a several files that are described below: The JavaScript file that defines the widget function (Widget.js) The template file that defines the widget’s user interface(Widget.html) The widget’s configuration file (config.json) The widget's manifest file which is needed for publishing(manifest.json) The widget’s i18n strings file for internationalization of strings if needed (nls/strings.js) The widget’s style file if you're a fancy guy and like border-rounding, background-images, sliders and circles (css/style.css) In the end the structure of your widget should look something like this: As you may have noticed the structure also contains a folder called setting. As we want to customize the behavior of our widget we need to define some settings and interact with them. The Code Let's get through the files. We will let the user define, whether or not he/she would like to see the scale, the zoom level or both. This is done in the setting.html The Setting.html / Setting.js Therefore we will show two Checkboxes. As I like it plain Vanilla (and didn't got the dijit checkboxes to work 😉 ) we use HTML input checkboxes: <div>
<div class="title">Zoom Level Info</div>
<div class="row">
<input type="checkbox" id="ZoomSetCheckbox"></input>
<p style="display:inline">Show Zoom Info</p>
</div>
<div class="row">
<input type="checkbox" id="ScaleSetCheckbox"></input>
<p style="display:inline">Show Approx. Scale</p>
</div>
</div> But as this is a widget and we would like to store the default settings in a configuration file called config.json we need to connect the setting.html with the configuration file using the settings.js file. Therefore we will not only use ids of the buttons (each widget could have items with identical ids...) but also data connectors: <div>
<div class="title">Zoom Level Info</div>
<div class="row">
<input type="checkbox" id="ZoomSetCheckbox" data-dojo-attach-point="ZoomSetCheckbox"></input>
<p style="display:inline">Show Zoom Info</p>
</div>
<div class="row">
<input type="checkbox" id="ScaleSetCheckbox" data-dojo-attach-point="ScaleSetCheckbox"></input>
<p style="display:inline">Show Approx. Scale</p>
</div>
</div> Now we can communicate with the sates of the buttons. The setting.js file has two main functions: reading and writing the settings to the config.json file so the widget uses always the correct settings in a new app. The settings in the config.json file are to set as follow: {
"settings": {
"ZoomSetCheckbox": true,
"ScaleSetCheckbox": true
}
} As visible, the default state of the widget isto enable both checkboxes. The states of the config can be set and read using two functions: getConfig and setConfig (download the zip at the end of the post to get the whole script): define(['dojo/_base/declare','jimu/BaseWidgetSetting'],
function(declare, BaseWidgetSetting) {
return declare([BaseWidgetSetting], {
startup: function(){
this.inherited(arguments);
console.log(this.config);
this.setConfig(this.config);
},
setConfig: function(config){
this.config = config;
this.ZoomSetCheckbox.checked = config.settings.ZoomSetCheckbox;
this.ScaleSetCheckbox.checked = config.settings.ScaleSetCheckbox;
console.log("settings read from file: ");
console.log(config);
},
getConfig: function(){
settings = this.config.settings;
if (this.ZoomSetCheckbox) {
settings.ZoomSetCheckbox = this.ZoomSetCheckbox.checked;
}
if (this.ScaleSetCheckbox) {
settings.ScaleSetCheckbox = this.ScaleSetCheckbox.checked;
}
this.config.settings = settings;
console.log("settings write to file: " + settings);
console.log(this.config);
return this.config;
}
});
});
This reads the states of the button (this.XXXSetCheckbox) and writes it in the config for each app it is used in and reads the configuration according the used settings. The Widget.html / Widget.js As we covered the settings in the paragraph above let's concentrate on the main code in the Widget.html and Widget.js. We are only showing two lines in the widget: One line for the Zoom Level, one for the Scale. Therefore the Widget.html file is very short and consists of 2 lines: <div class="ZoomLeveLInfo">
<div data-dojo-attach-point="zoomInfoDiv">Zoom Level: <p style="display: inline;" data-dojo-attach-point="MapZoomInfo"></p></div>
<div data-dojo-attach-point="scaleInfoDiv">Approx. Scale: <p style="display: inline;" data-dojo-attach-point="scaleInfo"></p></div>
</div>
You can see a similar approach as in the Setting.html: We use data-dojo-attach-points to access the div element and alter the content of this div. We could do this inline but we stick to the structure and define the JavaScript part in the Widget.js. define([
'dojo/_base/declare',
'dojo/_base/lang',
'jimu/BaseWidget',
'dojo/on', //as we alter the div when the map is panned "on.extent-change"
'esri/map' //as we interact with the map
],
function(declare, lang, BaseWidget, on, Map) {
return declare([BaseWidget], {
baseClass: 'jimu-widget-zoomlevelinfo',
postCreate: function() {
this.inherited(arguments);
this.own(on(this.map, 'extent-change', lang.hitch(this, this._zoomInfo)));
this._zoomInfo();
},
startup: function() {
this.inherited(arguments);
},
_zoomInfo: function(){
scale = esri.geometry.getScale(this.map);
var json = this.config; //read the config
if (json.settings.ZoomSetCheckbox){ //show Zoom Info set to true
this.MapZoomInfo.innerHTML = this.map.getZoom(); //alter the inner HTML with the Zoom Info
} else {
dojo.destroy(this.zoomInfoDiv); //get rid of the div so it looks clean if not wanted
}
if (json.settings.ScaleSetCheckbox){
this.scaleInfo.innerHTML = "1:" + Math.round(this.map.getScale()/1000)*1000 ;
} else {
dojo.destroy(this.scaleInfoDiv);
}
}
});
});
The Manifest.json To implement the new custom app in a web app we need to create a file called manifest.json. This file holds the basic information about the name and main settings: {
"name": "ZoomLevelInfo",
"platform": "HTML",
"version": "2.8",
"wabVersion": "2.8",
"author": "Esri Deutschland GmbH // Riccardo Klinger",
"description": "This is a widget to show scale and zoom level of the underlying map.",
"copyright": "",
"license": "http://www.apache.org/licenses/LICENSE-2.0",
"properties": {
"inPanel": false, //as it is off-panel
"hasUIFile": true, // we do have a settings ui file
"supportMultiInstance": false´// we just allow one instance of the widget.
}
} Now we can test the widget. Testing the Widget As we created the main widget we should test it in our WAB Developer installation now. Therefore place the whole folder (in our case ZoomLevelInfo) in the client widget folder of the WAB (in my case "C:\WebAppBuilderForArcGIS\client\stemapp\widgets"). For the test, restart the Web AppBuilder and create a new application: The widget is now part of the Web AppBuilder and can be selected as a widget: Hosting the Widget If your tests were successful you can host the widget on a server and implement it in ArcGIS Online or your ArcGIS Enterprise (>10.5) environment as a custom widget. If you own the portal you might also want to add the folder inside the WAB of the portal (in my case "C:\Program Files\ArcGIS\Portal\apps\webappbuilder\stemapp\widgets"). Download the Example If you want to use the example, use github here. Or you download it directly below.
... View more
06-11-2018
08:52 AM
|
7
|
0
|
5314
|
POST
|
I really love the idea of designing your own vector tiles. Yet I also like to work with solutions in my own environment / on premise. Are there plans to make this a standalone application?
... View more
05-18-2018
12:51 AM
|
2
|
1
|
591
|
POST
|
I would love to see this feature as well as I am trying to visualize the change of land prices in urban areas ofver the last 10 years.
... View more
04-19-2018
01:36 AM
|
5
|
0
|
2505
|
POST
|
I just computed multiple ring buffers for a point in EPSG 4326 with a 100m intervall for a maximum distance of 1000m. I was observing a calcualtion time of approx. 50s within ArcMap, 4min in ArcGIS Pro and 40s as a processing service. Yet I would love to see the same performance in ArcMAP as well as ArcGIS Pro because I am shipping a toolbox with this arcpy command: def createDistances(incr,maximum):
#as we have the raster now, we will need a multiple ring buffer:
distances = []
for dist in range(incr,maximum+1,incr):
distances.append(str(dist))
distancesString = ";".join(distances)
return distancesString
distancesString = createDistances(100,1000)
arcpy.analysis.MultipleRingBuffer("Your single point feature", r"your ring buffer name", distancesString, "Meters", "distance", "ALL", "FULL") Are there ways to increase calculation times in ArcGIS Pro? I am running this on a X270 with this spec: Intel(R) Core(TM) i7-7600U CPU @ 2.80GHz, 2904 MHz, 2 Cores, 4 logical processors 16Gb Ram, SSD no dedicated GPU.
... View more
02-18-2018
08:41 AM
|
2
|
5
|
3299
|
Title | Kudos | Posted |
---|---|---|
5 | 08-29-2018 01:32 AM | |
5 | 04-19-2018 01:36 AM | |
1 | 06-14-2018 05:03 AM | |
3 | 08-19-2018 01:12 PM | |
4 | 08-03-2018 05:22 AM |
Online Status |
Offline
|
Date Last Visited |
11-11-2020
02:25 AM
|