odoe

Zoom To Anything

Blog Post created by odoe on Dec 10, 2014

zoom.jpg

Let’s keep this one short and sweet. How many times have you been working with the ArcGIS API for JavaScript and had to zoom to a feature. Have you scratched your head at some of the odd behavior of using map#centerAndZoom? Maybe you found yourself writing some switch/case or if/else statements to zoom the map based on whether or not you were zooming to a point or a polygon or a polyline.

 

Read the docs

Before we look at how to easily zoom to features, let’s look at why the centerAndZoom method the map hates you.

The method signature of centerAndZoom is centerAndZoom(mapPoint, levelOrFactor). What does levelOrFactor mean? Let’s look at the definition.

 

"When using an ArcGISTiledMapServiceLayer, the map is zoomed to the level specified. When using a DynamicMapServiceLayer, the map is zoomed in or out by the specified factor. For example, use 0.5 to zoom in twice as far and 2.0 to zoom out twice as far."

 

That’s much better. No? Still confused? Ok, basically if you are using an ArcGISTiledMapServiceLayer you can zoom the map in to a specific level of detail. Think the zoom slider on a map. However, if you are using a DynamicMapServiceLayer then it gets wonky. Now you can zoom in by a twice as much or out twice as much, but you need to specify the factor in decimal, so think percentages. I’m guessing you want to zoom in twice as much, so 0.5 seems like a safe bet, but if you did 0.25, that would be what, zoom in 4 times as much.

 

Do the work

Ok, with that in mind, you pretty much know what to do. I put together this little module a while ago when I got tired of writing the same thing over and over again and checking for geometry types and different scenarios. I’m willing to bet this will handle 90% of the situations you run into (I have no hard numbers to back this up, but trust me).

define([
  'dojox/lang/functional/curry'
], function(curry) {
  var zoom = curry(function(map, geometry) {
    if (geometry.type === 'point') {
      var mz = map.getMaxZoom() ;
      if (mz > -1) {
        map.centerAndZoom(geometry, mz - 2);
      } else {
        map.centerAndZoom(geometry, 0.25);
      }
    } else {
      map.setExtent(geometry.getExtent());
    }
  });
  return zoom;
});

 

Here is a live demo.

 

So you basically check if the geometry is a point. If it’s a point, you get the maximum zoom level of the map. At this point, you have two choices:

  1. If it’s greated than -1, zoom to that level and subtract 2 so you don’t zoom too close. Or not, that’s up to you.
  2. Zoom in at a factor of 0.25, meaning zoom in 4 times the current zoom level.

If the geometry is not a point, it’s easy, polygons, polylines and multipoints can return an extent, so you can just the maps current extent to the extent of the geometry.

 

Kick it up a notch

This works pretty good, but let's say you want a more flexible solution. The above version works fine when passed a geometry, but how about a graphic or an array of graphics? These are all valid objects that you may want to zoom to. So for that, let's build up a module that could handle just about anything you throw at it.

  define([
    'dojox/lang/functional/curry',
    'esri/graphic',
    'esri/graphicsUtils'
  ], function(curry, Graphic, gUtils) {
    var zoomToPoint = function(m, geometry) {
      var mz = m.getMaxZoom();
      if (mz > -1) {
        return m.centerAndZoom(geometry, mz - 2);
      } else {
        return m.centerAndZoom(geometry, 0.25);
      }
    };
    var zoomToGeom = function(m, geometry) {
      if (geometry.type === 'point') {
        return zoomToPoint(m, geometry);
      } else {
        return m.setExtent(geometry.getExtent());
      }
    };
    var zoomToGraphics = function(m, graphics) {
      if (!graphics.length) return;
      if (graphics.length > 1) {
        return gUtils.graphicsExtent(graphics);
      } else {
        var g = graphics[0];
        var geometry = g.geometry;
        if (geometry.type === 'point') {
          return zoomToPoint(m, geometry);
        } else {
          return m.setExtent(gUtils.graphicsExtent(graphics));
        }
      }
    };
    var zoomToGraphic = function(m, graphic) {
      return zoomToGeom(m, graphic.geometry);
    };
    var zoom = curry(function(m, gs) {
      if (gs.type) {
        return zoomToGeom(m, gs);
      }
      if (gs instanceof Array) {
        return zoomToGraphics(m, gs);
      }
      if (gs instanceof Graphic) {
        return zoomToGraphic(m, gs);
      }
    });
    return zoom;
  });

 

This module utilizes the esri/graphicsUtils module to handle arrays of Graphics, it can handle a single Graphic or Geometry. Click here to view a demo of this module in action.

 

Bam done!

Woah woah. What’s this curry nonsense? Ok, so I snuck this one in. Using curry basically allows you to partially evaluate functions. Which means, you pass the map to the function before you need to pass the geometry. Here is an explanation of curry in JavaScript.

This isn’t a hard requirement, but it does spice things up a bit.

 

Play with it, modify it, abuse it and code.

Outcomes