GIS Life Blog - Page 5

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Latest Activity

(462 Posts)
ReneRubalcava
Esri Frequent Contributor

minions.jpg

I'm sitting here at the keynote (CEO of Taqtile) for the 10th annual Esri Developer Summit. I've spent the last couple of days in sessions, and the plenary and enjoying all the geo-goodness of those around me.

There are a lot of developer conferences throughout the year, but what makes the Developer Summit special is the industry focus and niche of this community. The other big geodev conference is FOSS4G, which unfortunately was at the same time as devsummit this year. But that's okay. It's always a blast to see some familiar faces and to put some faces to those that I interact with online in some way.

I really enjoy the work I do. I like the problem solving and I particularly like working with maps. I also really enjoy contributing to the geo-community as a whole. Some folks here have stopped to simply say thank you for those contributions. I just want to say thank you for reading.

Go hack at things and build cool stuff folks.

For more geodev tips and tricks, check out my blog.

more
0 0 976
ReneRubalcava
Esri Frequent Contributor

beginner_eleaf.jpg

If you've never used Leaflet, it's a fantastic lightweight mapping library that has grown in popularity over the years, mainly due to the simplicity of the API. It has tons of plugins and controls that can be used with it, including the Esri-Leaflet plugin that let's you work with ArcGIS Server and ArcGIS Online services.

There is plenty of great stuff in the examples for Esri-Leaflet on how to get started with it, but sometimes I get asked how a developer might incorporate it into their development workflow. Examples are great for getting familiar with the project, but as an application grows, things could get a little complicated. I just wanted to show you how you might get started from scratch with a more robust solution.

Get the bits

For this demo, you're going to need node and npm. Let's not worry about the whole io.js thing. Once you have node and npm installed, create a directory on your machine for your project. It doesn't need to be on a local server, we'll take care of that with node. With that directory created, open a terminal in it and run the following command in your command line tool of choice: npm init

Accept the defaults for the prompts you are given. When that is done, you will have a package.json file in your directory. Now we need to install a couple of modules to help us during development, including Leaflet and Esri-Leaflet.

npm install watchify --save-dev

npm install http-server --save-dev

npm install leaflet --save

npm install esri-leaflet --save

Basic setup

With the modules we'll need installed, let's get some basic files set up.

Create an index.html file in your directory that will look like this, nothing fancy.

<!DOCTYPE html>
<html>
    <head>
        <title>Starting Esri Leaflet</title>
        <link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" />
        <link rel="stylesheet" href="css/main.css" />
        <meta http-equiv="content-type" content="text/html; charset=utf-8" />
        <meta name="viewport" content="initial-scale=1.0 maximum-scale=1.0">
    </head>
    <body>
        <script src="bundle.js"></script>
    </body>
</html>

Next create a css directory and let's make a simple main.css file in there.

html, body, .map-div {
  width : 100%;
  height : 100%;
  margin : 0;
}

Next create a src directory in your project and inside the src directory create an index.js file and a popup.js file. We'll come back to this file in a second.

Automate it

Now open the package.json file and let's make some edits to the scripts section.

"scripts": {
  "start": "http-server",
  "watch": "watchify src/index.js src/popup.js -o bundle.js -dv",
  "start-dev": "npm start | npm run watch"
}

This scripts section will let you run commands using npm. This is really handy for setting up quick development environments. The start section will run a local development server on localhost:8080. The watch section will check for changes to the files specified and output them using browserify to a bundle.js file that will compile all the required JavaScript into a single file.

Note - If you are not on Windows, you should be able to change the line to watchify src/*js instead of listing each file, but on my Windows machine, I needed to list each file to watch.

At this point, your development environment is all set up to automate bundling your JavaScript and to run a local server.

Run the following command and let's move on: npm run start-dev

Write some code

Open the src/popup.js file and lets write some code.

var L = require('leaflet');
var popupTemplate = '<h3>{NAME}</h3>{ACRES} Acres<br><small>Property ID: {PROPERTYID}<small>';
module.exports = function(feature){
  return L.Util.template(popupTemplate, feature.properties);
};

If this looks odd to you, don't worry. This is commonjs syntax for writing modular JavaScript. Here is a great intro to commonjs modules on egghead.io. If you are used to using AMD with Dojo in the ArcGIS API for JavaScript this might throw you off a bit, but it works pretty nicely. In a module, whatever you define as the module.exports is what get's exported from this module. In this case, we are exporting a function that returns a popup template for Leaflet.

Now open up src/index.js and lets edit this file.

var L = require('leaflet'); // bring in Leaflet
require('esri-leaflet'); // add esri-leaflet
var popupTemplate = require('./popup.js'); // bring in our popup we just defined
var node = L.DomUtil.create('div', 'map-div', document.body); // create the node for the map
var map = L.map(node).setView([45.528, -122.680], 13);
L.esri.basemapLayer('Gray').addTo(map);
var parks = new L.esri.FeatureLayer('http://services.arcgis.com/rOo16HdIMeOBI4Mb/arcgis/rest/services/Portland_Parks/FeatureServer/0', {
  style: function () {
    return { color: '#70ca49', weight: 2 };
  }
}).addTo(map);
parks.bindPopup(popupTemplate);

All of this is standard code you would see in the Esri-Leaflet examples. The only difference is we are using require to add modules to the application. We even added the popup.js file we created earlier. Notice, require behaves differently here from how it does in Dojo.

What just happened?

You may not have noticed, but while you were editing your JavaScript files, you should have seen some messages in your terminal about a bundle.js file being created. That's because the npm scripts we wrote earlier are set up to watch for any changes in these JavaScript files and to recompile the them into single bundle.js file your application needs. If you open your browser to localhost:8080 *crosses fingers* you should seem something like this.

eleaf-sample.jpg

Congratulations! You just set up a very simple development environment to build your Esri-Leaflet applications. As you add more modules or organize your code, just edit the watch script in package.json as needed and keep on coding.

Here is the full application on github.

This is a very simplistic set up, but honestly you could do a lot with the npm scripts as they are currently designed. If you wanted to dive in with more robust tools, you could look at things like gulp, grunt or webpack. Get knee deep and learn to love your build tools.

For more geodev tips and tricks check out my blog.

more
1 0 2,430
ReneRubalcava
Esri Frequent Contributor

dojo-bootstrap-logo.png

A while back I did a post on my own blog on using Dojo Bootstrap with the ArcGIS API for JavaScript. I showed how you can incorporate it to do an autocomplete search that works pretty well and looks good. I've used Dojo Bootstrap pretty extensively in my app development and I've found a few quirks that require a little work on my part. If you layout your HTML with the property attribute tags and load the library you should not have any real issues beyond a little learning curve. I've found that I've had to do a couple of work-arounds when I wanted to programatically create elements as widgets. An easy demonstration of this can be seen when creating a Modal.

Modals, you're own way

I typically want to treat my Modal popups as a widget and that means I may want to customize the look of the Modal a little bit. In the sample I'll show, this is the template I use for the widget.

var tpl = [
  '<div class="modal fade popup-container" id="myModal" tabindex="-1" role="dialog"',
  'data-dojo-type="Modal" data-dojo-props="header:My Modal, modalClass: fade"',
  'aria-labelledby="myModalLabel" aria-hidden="true" data-dojo-attach-point="modalNode">',
  '<div class="modal-dialog">',
    '<div class="modal-content" data-dojo-attach-point="contentNode">',
      '<div class="modal-header">',
        '<h4 id="myModalLabel"></h4>',
        '<span data-dojo-attach-point="labelNode">${title}</span>',
        '<a href="javascript:void(0)" class="glyphicon glyphicon-remove pull-right popup-close" data-dismiss="modal" aria-hidden="true"></a>',
      '</div>',
      '<div data-dojo-attach-point="bodyNode" class="modal-body">',
      '</div>',
    '</div>',
  '/div>',
'</div>'
].join("");

A lot of this is pretty standard, but I wanted to dynamically set the title and add a bodyNode attach-point that I could add the content to. Instead of interacting directly with the Modal module, I wrap it in a separate widget that allows me to do things like set the title and update the content. This custom widget for the Modal looks like this.

var Popup = declare([_WidgetBase, _TemplatedMixin, Evented], {
  templateString: tpl,
  loaded: false,
  constructor: function() {
    this.set('content', '');
    this.set('title', 'Popup Window');
  },
  postCreate: function() {
    this.modal = new Modal(this.domNode);
    var watchContent = this.watch('content', function(_, __, value) {
      domConstruct.empty(this.bodyNode);
      this.bodyNode.appendChild(domConstruct.toDom(value));
    }.bind(this));
    var watchTitle = this.watch('title', function(_, __, value) {
      this.labelNode.innerHTML = value;
    }.bind(this));
    var onHide = on(this.modal.domNode, 'hide.bs.modal', function() {
      this.emit('hide', {});
    }.bind(this));
    this.own(watchContent, watchTitle, onHide);
    this._init();
  },
  show: function() {
    this.modal.show();
  },
  hide: function() {
    this.modal.hide();
  },
  _init: function() {
    this.set('loaded', true);
    this.emit('loaded', true);
  }
});

So what's happening here is you create a new Modal using the domNode of the widget as the target. You set up a watcher for when the Modal is hidden and propagate that event up from the widget. This required looking at the tests for the Modal to find event names, as some of the docs are still being updated. Then you set up watchers for the title and the content, the latter being converted to a DOM element and added to the bodyNode of the widget.

The result will look similar to this.

modal-sample.jpg

To demo this, I just took an existing InfoWindow sample and modified it in this JSBin.

Dojo Bootstrap Sample

There's not much happening in that Modal window, but think about what you could do with that extra real estate. You could display image attachments, or provide nicely formatted details of the data or related table data. You'd have room to even add other widgets in there to display nice charts in the window. I use this to provide full edit forms with nice big buttons and even other Modal popups as picklists. The power is in your hands.

Hack and slash

This may not be groundbreaking work here, but adding little touches like moving InfoWindow content to a Modal popup are the types of things that add a bit of flair to your applications. You could use dijit/Dialog as well and tweak the styling as well if you like, the idea is to test this type of interaction out and see if it provides a more fluid experience for your users. You could even style the Modal popups to take up the whole page and provide some nice looking transitions on a mobile device. You're writing the code, make it you own. Big thanks to Tom Wayson‌ for helping me dig into the Dojo Bootstrap guts to get things done.

For more geodev tips and tricks, check out my blog and hack your maps.

more
2 1 2,330
ReneRubalcava
Esri Frequent Contributor

domains.jpg

So you're chugging away at your awesome ArcGIS JavaScript application. You have some nice custom tools in place, you may even have some search capabilities going on, this thing looks cool. You go to click on a feature on the map and you've got all the important field information... except that you have numbers where maybe values should be showing up?

sad_infowindow.jpg

Well that the heck is that all about? What does that number even mean? So you go and look at the map service and you see that this field has a domain associated with it. Maybe you look at another application where this service is edited and you notice that the attribute inspector in the editor has the correct domain values and now you're just scratching your head wondering how to get those values. Don't worry, they're there...somewhere.

Field Fishing

Lucky for us, the FeatureLayer has a fields property we can use to get not only the domain values, but also the alias names, which are nice human readable descriptions of the field. It's not exactly in the optimal format to just drop in and use, but with a little work you can make it handle your data with ease. You may want to create a hash table to easily associate the coded values with the domain values.

var fieldMap = {};
featureLayer.on('load', function() {
  // iterate the fields of the Layer
  featureLayer.fields.filter(function(x) {
    return !!x.domain; // force value to boolean. only care about domains here
  }).map(function(x) { // map fields with domains
    fieldMap[x.name] = {}; // hash table
    fieldMap[x.name].values = {}; // will hold domain values
    fieldMap[x.name].alias = x.alias; // nice display name
    x.domain.codedValues.map(function(a) { // map over the codedValues
      fieldMap[x.name].values[a.code] = a.name; // save domain
    });
  });
});

The key here is to iterate over the fields of the layer and store the coded value as the key to the domain value. This will make lookup of the values much easier in the next step.

We have a method that defines the content of the popup when it gets clicked. We can use the hash table of domain values to make this a simple task.

function getTextContent(graphic){
  // get keys of the attributes
  // *Note - you may need a shim for your browser
  var elems = Object.keys(graphic.attributes).map(function(x) {
    var attr = graphic.attributes;
    var field = fieldMap;
    if (field) {
      return "<b>" + field.alias + "</b>: " + field.values[attr];
    } else {
      return false;
    }
  }).filter(function(x) { return x; }); // only return valid results
  return elems.join('<br />');
}

So in this sample, we use Object.keys(note - you may need to shim this method) to get the keys of the attributes. You can use the keys to get the matching set of coded and domain values, along with the alias for the field, and compose a simple DOM element to display in the InfoWindow. The result of this technique now display the InfoWindow like below.

happy_infowindow.jpg

You can see a full demo of this sample here.

It just need a little massaging

Sometimes when working with data from ArcGIS Server and such you may need to massage your results to display as you want them to. It's not difficult to do and once you do this a couple of times, it becomes like second nature, but I know from experience how frustrating it can be to try and navigate the documentation to try and solve this simple task. I hope this saves you a step or two. This isn't necessarily the only way to accomplish this, but I thought it was the most straight-forward example.

For more tips and tricks on geodev, check out my blog.

more
0 0 1,362
ReneRubalcava
Esri Frequent Contributor

table_contents.png

Ok, let's talk the ToC.

Adding a Table of Contents/Legend widget to ArcGIS web maps is one of those things that unfortunately is a necessary evil. I really think you should strive to design your map and your application in such a way that a ToC isn't needed. But sometimes, in those worst times, you have to do what you have to do.

I'm guilty of it. I'm aware that users may want it and developers have to deal with it. I even added an example in a chapter to my ArcGIS WebDev book.

Questions in the forum always seem to pop up about it. This ToC Widget from nilu appears to be pretty popular. It's a neat widget, tons of features, generally good stuff.

Learn to walk the ToC

But you as a developer should have an idea of how a ToC widget is built. I explain the concepts in the above linked sample chapter from my book. But here are the basic steps.

  • Make a request to the legend endpoint of the MapService.
  • Parse the legend response into a sweet looking list of the layers
  • Wire up click events to turn layers on/off
  • Bonus - Wire up click events to sub layers on/off
  • Do the happy dance!

happy_dance.gif

That's all there is to it. Simple right?

There is probably a dozen different ways you could accomplish this. You could use the dijit/Menu, you could a dijit/Tree, you could just use regular DOM elements, take your pick.

Here is some code that does just this.

define([
  'dojo/_base/declare',
  'dojo/Deferred',
  'dojo/on',
  'dojo/topic',
  'dojo/query',
  'dojo/dom',
  'dojo/dom-attr',
  'put-selector',
  'dojo/dom-class',
  'dojo/Evented',
  'esri/lang',
  './layerservice',
  'dojox/lang/functional/curry',
  'dijit/_WidgetBase',
  'dijit/_TemplatedMixin',
  'dojo/text!./templates/layertoc.tpl.html'
], function(
  declare, Deferred, on, topic,
  query, dom, domAttr, put, domClass,
  Evented, esriLang,
  getLayers, curry,
  _WidgetBase, _TemplatedMixin,
  template
) {
  var labelName = curry(function(a, b) {
    if (b.label && b.label.length > 1) {
      return b.label;
    } else if (a.layerName && a.layerName.length) {
      return a.layerName;
    } else {
      return 'Layer Item';
    }
  });
  var sub = esriLang.substitute;
  var layertitle = '<span class="pull-right">${title}</span>';
  return declare([_WidgetBase, _TemplatedMixin, Evented], {
    templateString: template,
    postCreate: function() {
      var node = dom.byId('map_root');
      put(node, this.domNode);
      var map = this.get('map');
      var layerIds = this.get('layerIds');
      // map over the layer ids and pull
      // the layers designated as part of legend
      this.tocLayers = map.layerIds.map(function(x) {
        if (layerIds.indexOf(x) > -1) {
          return map.getLayer(x);
        } else {
          return false;
        }
      }).filter(function(a) { return a; });
      // map over those layers and create some DOM element containers
      this.tocLayers.map(function(x) {
        var visible = x.visible ? 'glyphicon-ok' : 'glyphicon-ban-circle';
        var panel = put(this.tocInfo, 'div.panel.panel-default');
        var pheading = put(panel, 'div.panel-heading');
        var ptitle = put(pheading, 'h4.panel-title');
        put(
          ptitle,
          'span.glyphicon.' + visible +
            '.layer-item[data-layer-id=' + x.id + ']'
        );
        var node =
          put(ptitle,
              'span',
              { innerHTML: sub(x, layertitle) }
             );
        this._getDetails(x, node, panel);
      }.bind(this));
      // this will handle turning the whole service on/off
      var layerHandle = on(this.tocInfo, '.layer-item:click', function(e) {
        e.preventDefault();
        e.stopPropagation();
        domClass.toggle(e.target, 'glyphicon-ok glyphicon-ban-circle');
        var id = domAttr.get(e.target, 'data-layer-id');
        var lyr = map.getLayer(id);
        lyr.setVisibility(!lyr.visible);
      });
      // this will turn individual layers on/off
      var itemHandle = on(this.tocInfo, '.sublayer-item:click', function(e) {
        e.preventDefault();
        e.stopPropagation();
        domClass.toggle(e.target, 'glyphicon-ok glyphicon-ban-circle');
        var id = domAttr.get(e.target, 'data-layer-id');
        var subid = parseInt(domAttr.get(e.target, 'data-sublayer-id'));
        var lyr = map.getLayer(id);
        var lyrs = lyr.visibleLayers;
        var visibleLayers = [];
        // this bit will adjust the visible layers based on what was clicked
        if (lyrs.indexOf(subid) > -1) {
          visibleLayers = lyrs.filter(function(x) {
            return x !== subid;
          });
        } else {
          visibleLayers = lyrs.concat([subid]);
        }
        lyr.setVisibleLayers(visibleLayers);
      });
      this.own(layerHandle, itemHandle);
    },
    // do a quick check that a URL has been provided
    _getDetails: function(layer, node, panel) {
      if (!layer.url) { return; }
      var pbody = put(panel, 'div.panel-body');
      this._getLegend(layer, pbody);
    },
    // here is the workhorse
    _getLegend: function(layer, pbody) {
      var url = layer.url + '/legend';
      var id = layer.id;
      // this is a just a wrapper module I use for
      // esri/request. see source at https://github.com/odoe/esri-layertoc-sample
      getLayers(url).then(function(layers) {
        var tbl = put(pbody, 'table.table');
        // iterate over the layers and
        // add items to the table
        layers.map(function(a) {
          var lbl = labelName(a);
          var layerId;
          var hasLayerId = false;
          if (a.hasOwnProperty('layerId')) {
            hasLayerId = true;
            layerId = a.layerId;
          }
          // iterate over the legend and add items
          // to the table-row
          a.legend.map(function(b) {
            var tr = put(tbl, 'tr');
            if (hasLayerId) {
              hasLayerId = false;
              var lyrCheck = put(
                'span.glyphicon.glyphicon-ok' +
                '.sublayer-item[data-layer-id=' + id + ']' +
                '.[data-sublayer-id=' + layerId + ']'
              );
              put(tr, 'td', lyrCheck);
            } else {
              put(tr, 'td');
            }
            var td1 = put(tr, 'td.layer-image');
            put(tr, 'td', {
              innerHTML: lbl(b)
            });
            // I just add the base64 image, but you could also
            // use the URL to image provided in Legend endpoint
            put(td1, 'img', {
              src: 'data:image/png;base64,' + b.imageData
            });
          });
        });
      }, function(err) { console.debug('error in request', err); });
    }
  });
});

Woah, that's a lot of code. Hey you wanted to learn about a ToC widget, that's going to take a bit of code. I added some comments in there to help you out. You might be able to break out some of the functionality into smaller modules, but I'll leave that up to you.

I'm using the put-selector in this sample to create DOM elements (it's included in the ArcGIS JS API) just because I've found it makes more sense for me when composing DOM creation. I'm also using Bootstrap to make it look nice, which is where some of the css class names are defined.

When it's all said and done, this sample will look something like this.

toc-sample.jpg

Pretty snazzy

What this does is turns off individual services and the individual layers in the visibleLayers. This sample is only set up for an ArcGISDynamicMapServiceLayer, but you could tweak it for FeatureLayers and if you're bold add support for custom renderers. I try to avoid this when I can as I always seem to muck something up, but it can be done. Good luck. I remember this being a lot harder a long time ago when I did this in Flex, as I don't think the REST API had a Legend endpoint back then, so this is actually easier than it would have been.

I just wanted to give you a decent overview of how you can go about accessing the legend endpoint of a map service to pull all the data you need to make your own ToC widget. Maybe this will help you troubleshoot issues you have using other ToC widgets. It's a good exercise in learning to display data in the DOM and sometimes, you may just need a ToC... maybe.

The full source code for this sample can be found here.

Be sure to check out my blog for more geodev tips and tricks!

more
1 6 2,451
ReneRubalcava
Esri Frequent Contributor

overview_map.jpg

Oh the mighty OverviewMap. This is a classic Dijit in the ArcGIS API for JavaScript. I'm a little torn on the usefulness of an OverviewMap. On one hand, I never include it an app by default and 95% of the time, no one ever asks for it. But, there is always that 5%. I personally don't think it adds much to a map application, it sort of falls into the same category as keeping a zoom history. It's a carry-over from early days that at I think at some point all Esri devs just kind of decided to quietly let die.

You do what you have to do

Every now and then though, a valid use case comes up. For example, let's say you are managing a fleet of vehicles and you want to focus on a particular neighborhood in your application, but you'd still like to have an overview of the vehicles somehow. In this case, an OverviewMap could do just fine. So you try it only to realize you can't add layers to the OverviewMap. If you look at the docs, this widget is so old it doesn't even dispatch any events to hook into. Sure you can change the basemap, but not much else. But you're a developer, you're not going to let a silly thing like documentation dictate what you can do!

Embrace the aspect

There's a module in Dojo called dojo/aspect. The aspect module is similar to dojo/_base/connect (don't use this module anymore), but better. The aspect module can listen for methods that occur on an object and do something when they occur. Let that sink in for a second. It's like a catch-all tool to hack away with code you didn't write. Don't abuse it too much, but it can prove very useful at times.

After some poking around, I was able to find that the OverviewMap has a method called _activate that occurs when the map object of the OverviewMap is ready and is assigned to a (undocumented) property called overviewMap (thanks Yann Cabon). You can wait for this method to occur and interact with the overviewMap as you would with any map.

Go nuts

So how would you do this? Here is some sample code where you can use a different basemap and add some census data to the OverviewMap.

var fl = new FeatureLayer(blockPointsUrl);
var baseLayer = new ArcGISTiledMapServiceLayer(basemapUrl);
var overviewMapDijit = new OverviewMap({
  map: map,
  baseLayer: baseLayer,
  visible: true
});
var h = aspect.after(overviewMapDijit, '_activate', function() {
  h.remove();
  overviewMapDijit.overviewMap.addLayer(fl);
});
overviewMapDijit.startup();

So you basically need to wait for the _activate method of the OverviewMap to occur and at that point the overviewMap property of the widget will be ready for you to interact with and you can add other layers to it. Here is a JSBIN demo of this in action.

Hack your way to glory

Don't let a silly thing like lack of documentation or little roadblocks like not having events to listen for stop you from hacking away at modules in the ArcGIS API for JavaScript. I'm not saying rip it apart, but you can totally work within the confines of the framework while you extend some functionality. Have fun with it and experiment.

For more geodev tips and tricks, check out my blog!

more
3 3 1,312
ReneRubalcava
Esri Frequent Contributor

esri_phone.jpg

Esri provides a lot of resources and tools for ArcGIS Developers. You could be working in JavaScript, iOS, Android, or Java, They pretty much have you covered as far as SDKs go. You might be fairly comfortable in the SDK of your choice, but if you ever decided to go from JavaScript to Android, other than the language difference, you should feel pretty good about it. Why? Because it's all based on a single REST API.

We're all really saying the same thing

Regardless of the SDK you work in, under the hood it's all taking to the same person on the other end of the line, the REST endpoint of an ArcGIS Server, Portal or Online. This unified REST API is why you may have felt pretty familiar moving from Silverlight or Flex to JavaScript. Once you get past the initial language bump, the methods and practices all seem to work the same. A Feature has the same structure no matter what SDK you are using. Editing data is familiar no matter what language you are working in. Sure, some of the tools may differ, but it's all very familiar.

SDKs? Where we're going, we don't need SDKs

Because the REST API is the driving force to the ArcGIS development, you could theoretically write all your applications from scratch against the API without using any ArcGIS SDKs. Sometimes this becomes necessary when the features in an SDK may not be up to date with the latest features of the REST API. The more familiar you are with working with the REST API, the easier it is for you as a developer to overcome these hurdles.

It's for this same reason that you are not limited to having to use the ArcGIS JS API for your JavaScript development, you could use esri-leaflet. Esri-leaflet is able to pull in tiles and services from ArcGIS sources and work with them because under the hood it's speaking the same language as everyone else, the language of the REST API. It may not be as feature rich as the ArcGIS JS API, but it's also much more lightweight and could be perfectly suitable for your use case.

Need to interact with ArcGIS Services in a server side application? You can talk to the REST API directly just like this ArcGIS partial class library does. You can run your queries, customize the results and output the information as needed. You don't need to be limited to official SDKs and tools, the REST API is there for your abuse use.

Have fun with it

So go on, give it a shot. Next time you find yourself limited by the SDK of your choosing, try to see if you can interact with the REST API directly to get the desired results. Up your game and get things done.

For more tips and tricks, be sure to check out my geodev blog.

more
3 0 2,221
ReneRubalcava
Esri Frequent Contributor

touchwidget.png

Have you ever been working with your nice and shiny mobile web map and trying to do some sort of drawing or selecting an item on the screen only to realize you can't tell where your fat fingers have touched the map? Wouldn't it be cool if you could somehow get some feedback on how you are interacting with the map? I'll let you in on a dirty secret, it's pretty easy to do.

Touchy feely

A while ago I had a user comment that they had issues knowing if they were clicking on the map where they thought they were. Touch screens on mobile devices can be finicky sometimes and I have seen some slower devices be off my as much as an inch on where you thought you were touching on the screen.

To deal with this, I made a TouchWidget. What this widget does is simply listen for click events on the map and add a little circle, then after half a second or so, delete the marker. The idea isn't too difficult, but it can be a little jarring to just show and hide a marker. So you can use some of the graphics modules in Dojo to make the transition a little nicer. Here is a neat little tutorial on working with Dojo animations.

Here is the source code for the TouchWidget

define([
  'esri/layers/GraphicsLayer',
  'esri/graphic',
  'esri/symbols/SimpleMarkerSymbol',
  'dojo/on', 'dojo/fx', 'dojo/_base/fx', 'dojo/fx/easing',
  'dojo/aspect', 'dojo/_base/Color',
  'dojo/_base/declare', 'dojo/_base/lang',
  'dijit/_WidgetBase', 'dijit/a11yclick'
], function(
  GraphicsLayer, Graphic, SimpleMarkerSymbol,
  on, fx, coreFx, easing, aspect, Color,
  declare, lang, _WidgetBase, a11yclick
  ) {
  'use strict';
  var hitch = lang.hitch;
  return declare([_WidgetBase], {
    postCreate: function() {
      this.set('delay', this.settings.delay || 500);
      this._symInner = new SimpleMarkerSymbol(SimpleMarkerSymbol.STYLE_CIRCLE, this.settings.innerSize, null, new Color(this.settings.innerColor));
      this._symOuter = new SimpleMarkerSymbol(SimpleMarkerSymbol.STYLE_CIRCLE, this.settings.outerSize, null, new Color(this.settings.outerColor));
      this.touchLayer = new GraphicsLayer();
    },
    startup: function() {
      if (!this.map) {
        this.destroy();
        throw new Error('Must provide a map object to use TouchWidget');
      }
      if (this.map.loaded) {
        this._init();
      } else {
        on.once(this.map, 'load', hitch(this, '_init'));
    },
    // widget methods
    _fxArgs: function(graphic) {
      return {
        node: graphic.getNode(),
        duration: this.delay,
        easing: easing.expoOut
      };
    },
    _fxToCombine: function(graphicOuter, graphicInner) {
      return [
        coreFx.fadeOut(this._fxArgs(graphicOuter)),
        coreFx.fadeOut(this._fxArgs(graphicInner))
      ];
    },
    _onAspectAfterEnd: function(graphicOuter, graphicInner) {
      return lang.hitch(this, function() {
        this.touchLayer.remove(graphicOuter);
        this.touchLayer.remove(graphicInner);
      });
    },
    _onTimeOut: function(graphicOuter, graphicInner) {
      return hitch(this, function() {
        var combined = this._fxToCombine(graphicOuter, graphicInner);
        var f = fx.combine(combined);
        this.own(aspect.after(f, 'onEnd', hitch(this, this._onAspectAfterEnd(graphicOuter, graphicInner))));
        f.play();
      });
    },
    _onTouchClick: function(e) {
      var graphicOuter = new Graphic(e.mapPoint, this._symOuter);
      var graphicInner = new Graphic(e.mapPoint, this._symInner);
      this.touchLayer.add(graphicOuter);
      this.touchLayer.add(graphicInner);
      setTimeout(hitch(this, this._onTimeOut(graphicOuter, graphicInner)), this.delay);
    },
    // private methods
    _init: function() {
      this.map.addLayer(this.touchLayer);
      this.set('loaded', true);
      this.emit('load', {});
      // set up touch handlers
      this.own(on(this.get('map'), a11yclick.click, hitch(this, '_onTouchClick')));
    }
  });
});

What this widget is doing is setting up an inner and outer graphic symbol. The size of these symbols can be defined via the parameters passed to the widget. It also adds it's own layer to display these features so you don't have to worry about conflicting with what may be happening on the default GraphicsLayer of the map.

Animating nodes

The dojo/fx library doesn't know what a Graphic from the Esri library is, so you'll need to get the actual DOM node of the Graphic to interact with. You can do this via the graphic.getNode() method. Now you can set up a delay using setTimeout to gracefully remove the touch marker from the map. You just need to pass around the graphics in this chain of methods so they are disposed of properly.

You can see how this TouchWidget works in this demo.

So feel free to touch your maps and get more interactive. Check out my blog for more tips & tricks!

more
0 0 2,066
ReneRubalcava
Esri Frequent Contributor

custom_logo.png

One of the easiest things you can do to personalize your ArcGIS JavaScript map is to add a custom logo. Esri does it, why can't you?

Own it

Chances are you're building the app for a client, and a client can mean your own workplace, but I'm betting that client has brand and most likely a logo.

You work hard for your maps, so let people know it!

This isn't really difficult to do, it's just a little css and DOM insertion, but you could power it up and make it a reusable widget too. A very simple example may look something like this:

var LogoWidget = declare([_WidgetBase, _TemplatedMixin], {
  templateString: '<div class="${logoClassName}" data-dojo-attach-event="click:openLink"></div>',
  postCreate: function() {
    put(dom.byId('map_root'), this.domNode);
  },
  openLink: function() {
    window.open(this.get('url'), '_blank');
  }
});

What you have is a basic widget where you can use the put-selector to add the widgets DOM node to the page. I'm going to assume you want the logo within the map bounds, so I set it up to insert at the div with an id of "map_root". This is equal to the DOM id you gave to the DOM element you provided for the map, so if the map id is "map", it will contain a div with an id of "map_root". If my map id was "harry", it would contain a div with an id of "harry_root". I'm sure you get the idea.

Once you do that, you just initialize the widget with a URL and a class name you define in your css:

var logo = new LogoWidget({
  url: 'http://odoe.net/blog/',
  logoClassName: 'my-logo'
});

Style it

Then in the css for the widget you can define the size and location as well as the background-image:

.my-logo {
  display: inline-block;
  position: absolute;
  height: 36px;
  width: 125px;
  right: 75px;
  bottom: 5px;
  z-index: 30;
  background-color: white;
  background-image: url('http://odoe.net/blog/wp-content/uploads/logo_gray_sm.png');
  cursor: pointer;
}

*Note - You could switch all this up to use an <img> tag if you wanted.

logo_demo.jpg

There you have it! You have a logo in the map and you can click that logo to go to a website of your choosing.

You can view a demo of this sample and play around with it if you like. There's nothing stopping you from being obnoxious awesome and adding an animated gif as your logo either.

So customize your apps and maps and show off your skills!

more
0 0 2,064
ReneRubalcava
Esri Frequent Contributor

esri-labels.png

If you've used the LabelLayer before to annotate your map you are familiar with the fact that you can add labels that match up with your map data. This is a neat feature and I'm sure it's really cool to work with pseudo-annotations in your webmap, but why stop there?

Font Awesome for awesomeness

If you've never used Font Awesome, it's a great little CSS Toolkit to add some nice font icons to your web page. This is really useful if you need to build an editor or want to add a little bit of flavor to your site and it's really easy to use. If you've ever added custom fonts to your web page, you know that there's some boilerplate involved. You can get similar icons with Bootstrap and even use them both together.

It's also pretty easy to use font icons in Leaflet if you wanted to and I was curious how I could do the same thing with the ArcGIS API for JavaScript. When I first looked at this, there was no LabelLayer in the API yet. I recently had a use-case to use font icons as markers in a project so I dove right in. Turns out, I don't even need the LabelLayer, nor do I care.

TextSymbol and Font

So how do we accomplish this? With the introduction of the LabelLayer, there were a couple of other support modules added - Font and TextSymbol. These two modules are the key to awesome markers. There are a few steps you need to do to get this working.

1. The first thing you'll want to do is add a reference to the font awesome css to your page.

<link href="https://community.esri.com//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">

2. Define the Font for your TextSymbol.

var font = new Font("20pt", Font.STYLE_NORMAL, Font.VARIANT_NORMAL, Font.WEIGHT_BOLD,"FontAwesome");

3. Create a TextSymbol.

var sym = new TextSymbol("", font, treeColor);

What is that weird character I am passing to the TextSymbol? That is the character from Font Awesome I want to use. You'll need to copy and paste the character like any other, but you'll want to use the Font Awesome Cheatsheet for that.

4. Now you can assign the symbol to the graphic and add it to the map.

feature.setSymbol(sym);
map.graphics.add(feature);

Here is the full JavaScript for the above snippets.

require([
  "esri/map", 
  "esri/symbols/TextSymbol",
  "esri/tasks/query",
  "esri/tasks/QueryTask",
  "esri/symbols/Font",
  "esri/Color",
  "dojo/domReady!"
], function(
  Map, TextSymbol,
  Query, QueryTask, Font,
  Color
) {
  var map = new Map("map-div", {
    center: [-122.445, 37.752],
    zoom: 14,
    basemap: "gray"
  });

  var treeColor = new Color("#666");
  var treesUrl = "http://services.arcgis.com/V6ZHFr6zdgNZuVG0/arcgis/rest/services/Street_Trees/FeatureServer/0";
  var font = new Font("20pt", Font.STYLE_NORMAL, Font.VARIANT_NORMAL, Font.WEIGHT_BOLD,"FontAwesome");
  // you can use the cheatseeht to copy paste the font symbol
  // http://fortawesome.github.io/Font-Awesome/cheatsheet/
  var sym = new TextSymbol("", font, treeColor);
  var qTask = new QueryTask(treesUrl);
  var query = new Query();
  query.outFields = ["*"];
  //query.where = "TreeID = 0";
  query.where = "1=1";
  query.returnGeometry = true;
  qTask.execute(query).then(function(featureSet) {
    featureSet.features.map(function(feature) {
      feature.setSymbol(sym);
      map.graphics.add(feature);
    });
  });
});

Add some flair!

The result of doing the above may get you something like this.

fontmarkers.jpg

That's really cool, but it's not quite right. Groups of trees kind of blend right into each other. My inner-cartographer is wincing. It's ok, we can do something about that. The icons are added via an svg <text> tag, which means we can use CSS to style it.

text {
  text-shadow: -1px 0 white, 0 1px white, 1px 0 white, 0 -1px white;
}

Now you should get a map that displays the icons like below.

fontmarkers-outline.jpg BAM!

That looks pretty cool if I do say so myself. You find the full demo of this example here.

Use it in a renderer

Want to use it as a renderer for a FeatureLayer? No problem.

require([
  "esri/map", 
  "esri/symbols/TextSymbol",
  "esri/symbols/Font",
  "esri/Color",
  "esri/layers/FeatureLayer",
  "esri/renderers/SimpleRenderer",
  "dojo/domReady!"
], function(
  Map, TextSymbol, Font,
  Color, FeatureLayer, SimpleRenderer
) {
  var map = new Map("map-div", {
    center: [-122.445, 37.752],
    zoom: 14,
    basemap: "gray"
  });

  var treeColor = new Color("#666");
  var treesUrl = "http://services.arcgis.com/V6ZHFr6zdgNZuVG0/arcgis/rest/services/Street_Trees/FeatureServer/0";
  var font = new Font("20pt", Font.STYLE_NORMAL,
    Font.VARIANT_NORMAL, Font.WEIGHT_BOLD,"FontAwesome");
      // you can use the cheatseeht to copy paste the font symbol
      // http://fortawesome.github.io/Font-Awesome/cheatsheet/
  var sym = new TextSymbol("", font, treeColor);
  var renderer = new SimpleRenderer(sym);
  var fl = new FeatureLayer(treesUrl);
  fl.setRenderer(renderer);
  map.addLayer(fl);
});

You'll get the same result as above, but with less code. You can find an example of this here.

Why?

Are you asking yourself why bother with this? Why not just use PictureMarkerSymbol and be done with it? I'll tell you why. Fonts scale, pictures don't. Have you ever loaded a PictureMarkerSymbol and had some jagged edges or maybe it was a little blurry? Especially if you tried redefining the size to fit better with your map? Fonts, like SVG vector graphics, scale so that if they are displayed at 8pt or 50pt, they don't pixelate and that is well enough reason for me to use them. This works pretty well and looks good for mobile apps. Granted, the font icon can't be multiple colors or very complicated, but look over the icons alone in Font Awesome or better yet the Mapbox maki icon set and you'll see there is lots of good stuff you may be able to use as a marker on your map.

Check out my regular blog for more geodev tips & tricks!

Go forth and hack away folks!

more
6 2 3,656
109 Subscribers