adjones

Code to generalize geometries for better performance in IE

Discussion created by adjones on Mar 18, 2011
Hello,

I thought I'd give back to the forum for once by posting a bit of code that generalizes JSAPI polygon and polyline geometries so that graphics rendering in IE7 is a bit faster for very complex features.  This is a client side bit of code as I have found a server geoprocessing task to be too slow for my needs.

This does not use the Douglas-Peucker as I have not had time to work out how to get this working.  If anyone has the Douglas-Peucker algorithm for a projected coord system in Javascript then please post it as a reply to make our jobs easier.  What this code does is quickly go through the vertices and makes sure that all segments in the path or ring is less than the given minimum distance. I use 200 and I can get 20,000 vertex UK counties down to approx 2000 with little noticeable degradation in resolution until about 1:5000.  I don't want to spend too much time explaining what the exact method is so look at the code.

I have written it to work with British National Grid in mind so all distances are in meters but it should not be too difficult to modify that where needed.

I have also found that if the extent of the polygon/polyline is more than around 10 times the size of the map extent and has serveral hundred vertices then IE7 grinds to a halt.  I have built upon the code given by Sam Larsens in the old forum to try and mitigate this problem.

Please, if you use and modify the code to make it better than please post it here.

Thanks
Andy

/**************************

 Generalise geometries to speed things up if
 geometry is complex.

        created by: Andrew Jones

***************************/
var const_MaxVertexCount = 3000;

/* Use this to generalize an array of esri.Graphic */
function generaliseGeometries(graphicsArray, minDistance)
{
    for (var g in graphicsArray)
    {
        var graphic = graphicsArray[g]
        var geom = graphic.geometry;

        generaliseGeometry(geom, minDistance);
    }

}


/*Use this in an identifyTask onComplete */
function generaliseIdentifyResults(idResults, minDistance)
{
    for (var i in idResults)
    {
        var ir = idResults[i];
        generaliseGeometry(ir.feature.geometry, minDistance);
    }
}


function generaliseGeometry(geometry, minDistance)
{
    var geomArray;
    if (geometry.rings !== undefined)
    {
        geomArray = geometry.rings;
    }

    if (geometry.paths !== undefined)
    {
        geomArray = geometry.paths;
    }
    if (geomArray !== undefined)
    {

        //Need to test the complexity of the shape to make sure we don't try generalising something that can be rendered.
        var gLength = 0;
        for (var g in geomArray)
        {
            gLength += geomArray[g].length;
        }

        if (gLength > const_MaxVertexCount)
        {

            var newGeomArray = [];
            var vertexCounter = 0;
            var newVertexCount = 0;

            for (var g in geomArray)
            {
                if (g != "length")
                {
                    var pathOrRing = geomArray[g];
                    var newPathOrRing = [];
                    newPathOrRing.push(pathOrRing[0]);

                    var vX_start, vY_start, v_start = pathOrRing[0];
                    vX_start = v_start[0];
                    vY_start = v_start[1];

                    for (var vtx = 1; vtx < pathOrRing.length - 1; vtx++) // ignore the first and last vertex.
                    {
                        var vX, vY, v = pathOrRing[vtx];
                        vX = v[0];
                        vY = v[1];

                        if (lineDistance(vX_start, vY_start, vX, vY) > minDistance)
                        {
                            newPathOrRing.push(v);

                            //make the saved vtx the next start vtx to measure from
                            v_start = v;
                            vX_start = vX;
                            vY_start = vY;
                        }
                       
                    }
                    newPathOrRing.push(pathOrRing[pathOrRing.length - 1]);
                    
                    newGeomArray.push(newPathOrRing);
                    
                }
            }
            if (geometry.rings !== undefined)
            {
                geometry.rings = newGeomArray;
            }

            if (geometry.paths !== undefined)
            {
                geometry.paths = newGeomArray;
            }
        }
    }
}

function lineDistance(x1,y1,x2,y2)
{
 return Math.sqrt(Math.pow(vX - vX_start, 2) + Math.pow(vY - vY_start, 2));
}


/************************************
 Call this function after calling all your dojo.require
 functions for the esri jsapi but before you create you
 map object.

 This is an enhanced version of code by Sam Larsens.
 Sam's original code can be found here:

 http://forums.esri.com/Thread.asp?c=158&f=2396&t=301953

**********************************/
var sizeFactor = 10;
function fixForLargeGraphicIECrash()
{
    dojo.extend(esri.layers.GraphicsLayer, {
        //  override the _drawShape method
        _drawShape: function(feature)
        {
            var geom = feature.geometry,
            type = geom.type,
            map = this._map,
            me = map.extent,
            mw = map.width,
            mh = map.height,
            eg = esri.geometry,
            _mvr = map.__visibleRect;

            if (dojo.isIE)
            {
                if (geom._vertexCount === undefined)
                {
                    geom._vertexCount = 0;
                    var count = 0;
                    if (geom.rings !== undefined)
                    {
                        for (var r in geom.rings)
                        {
                            count += geom.rings[r].length;
                        }
                        geom._vertexCount = count;
                    }
                    
                    if (geom.paths !== undefined)
                    {
                        for (var p in geom.paths)
                        {
                            count += geom.paths[p].length;
                        }
                        geom._vertexCount = count;
                    }
                    
                    if (geom.points !== undefined)
                    {
                        for (var p in geom.points)
                        {
                            count += geom.points[p].length;
                        }
                        geom._vertexCount = count;
                    }
                }

                var gExtent = geom.getExtent();
                if ((geom._vertexCount > const_MaxVertexCount)
   || (me.getWidth() < (gExtent.getWidth() / sizeFactor)) || (me.getHeight() < (gExtent.getHeight() / sizeFactor)))
                {
                    var shape = feature.getDojoShape();
                    if (shape) { shape.removeShape(); }
                    feature._shape = null;

                    //The graphic will not draw this time.  This code will be rerun each time the map extent changes
      //You'll need to let the the user know the graphic won't appear this time.
      window.status = "Not all graphics could be rendered due their complexity."; 
   
                    return;
                }

            }
            if (type === "rect" || type === "extent")
            {
                var rect;
                if (type === "extent")
                {
                    rect = eg.toScreenGeometry(me, mw, mh, geom);
                    rect = {
                        x: rect.xmin - _mvr.x,
                        y: rect.ymax - _mvr.y,
                        width: rect.getWidth(),
                        height: rect.getHeight()
                    };
                } else
                {
                    var xy = eg.toScreenPoint(me, mw, mh, geom),
                          wh = eg.toScreenPoint(me, mw, mh, {
                              x: geom.x + geom.width,
                              y: geom.y + geom.height
                          });
                    rect = {
                        x: xy.x - _mvr.x,
                        y: xy.y - _mvr.y,
                        width: wh.x - xy.x,
                        height: xy.y - wh.y
                    };
                }
                if (rect.width === 0)
                {
                    rect.width = 1;
                }
                if (rect.height === 0)
                {
                    rect.height = 1;
                }
                feature._shape = this._drawRect(this._div, feature.getDojoShape(), rect);
            } else
            {
                if (type === "polyline")
                {
                    feature._shape = this._drawPath(this._div, feature.getDojoShape(), eg._toScreenPath(me, mw, mh, geom, -_mvr.x, -_mvr.y));
                } else
                {
                    if (type === "polygon")
                    {
                        feature._shape = this._drawPath(this._div, feature.getDojoShape(), eg._toScreenPath(me, mw, mh, geom, -_mvr.x, -_mvr.y));
                    }
                }
            }
        }
    });

}

Outcomes