ThinkGeo.com    |     Documentation    |     Premium Support

Incorrect Distances / Scales

Hi,


I'm having a problem with distances, which seems to be manifesting itself in two ways but probably has the same root cause.


Firstly, the scale shown on the maps doesn't seem to be correct. As an example, I've attached a couple of screenshots. The first shows an airport feature on a raster Virtual Earth map from maps.live.com.



Here, you can see the main runway is approximate 2km long.


The second screenshot shows the same feature in Virtual Earth, but this time loaded within Map Suite.



I know the tiles look slightly different (because MS have recently updated the images on maps.live.com), but the airport feature is the same size which indicates they are at the same logical scale (I've confirmed this with other roads/features as well). The scale bar in Map Suite however, now implies the main runway is about 3km long.


The second manifestion of the issue is when I'm rendering circles that have been stored with a central lat/lon and a radius in metres. These are now being rendered smaller in Map Suite that they used to be when we used a different 3rd party component, which is consistent with the behaviour described above (i.e. Map Suite thinks a given distance is smaller than it actually is).


Is this a problem in the component, or a misunderstanding on my part in the way I'm using it? All of the maps we're displaying are spherical Mercator, and the map units are set to metres. We're not seeing any issues with lat/lon positioning - just these problems with distances. I'm just using the standard ZoomLevels (1-20), not custom ones.


Best regards,


Gary



I find strange that you have the scale bar displaying incorrectly if you have the MapUnit of your map set to meter as it should be using Virtual Earth. Can you tell what the extent of the map is where you are zoomed in to the airport? I would like to test on my own with scalebar on that part of the map but I don’t know where that airport is located. Thank you.

Hi Val, 
  
 That particular airport is at: 
  
 Lat: 53.771899 
 Lon: -3.032999 
  
 I’ve done the same check with different areas throughout the UK though - I just used this one as the horizontal runway demonstrated the issue quite well. 
  
 I’ve also just double checked and I’m definitely using mapControl.MapUnit = GeographyUnit.Meter. 
  
 Best regards, 
  
 Gary 


I think I know what is going on. The scale bar is based on the coordinates of the Mercator projection which is a projection that grossly exagerates the distances at high latitudes. (see the article on Mercator projection: en.wikipedia.org/wiki/Mercator_projection


 Since, your airport is at a fairly high latitude in the UK, the Scale Bar seems to show an exagerated distance although it is calculated correctly based on the coordinates of the Mercator projection that are in meters. To correct that, we would have to have the scale bar based on a local projection and not on the Mercator projection. We can work on creating a Scale bar on a projection different from the Map Projection. What projection would you like us to use? UTM or some other UK projection. Keep in mind that with a custom Scale bar, it going to work only for  where the local projection is intended to be used.


Another approach would be to used a custom Scale Bar based on Lat/Long and that would work for the entire world. What approach do you want us to take for you?



Hi Val, 
  
 Many thanks for your reply. I understand the issue with the distortion introduced with the Mercator projection. 
  
 I’m not sure I understand what the implications are for the solution you’ve described though. Just to confirm that we use both Virtual Earth maps and shapefiles that have been reprojected from WGS84 into Mercator. Most of the data we plot is stored in WGS84 degrees, but we reproject it into Mercator using the managed projection library to display it on the map. 
  
 We’d need something that works for the entire world, as we use maps for both Northern and Southern hemispheres. If we have a custom scale bar based on lat/lon will that work with our Mercator shape files and VE maps? 
  
 Best regards, 
  
 Gary

Ok, tomorrow I will present to you the solution of a custom Scale Bar based on Lat/Long that will work Spherical Mercator map (VE, Google maps etc). And you will also see how to extend the ScaleBarAdornmentLayer for building such a custom scale bar.  It will be in the Code Community for everybody to take advantage of. Thank you for your idea.

Hi Val, 
  
 Many thanks - this is much appreciated. 
  
 Best regards, 
  
 Gary 


Please check out the Code Community project Custom Scale Bar code.thinkgeo.com/projects/show/customscalebar. It shows how to construct a Scale Bar based on Geodetic (WGS84) for a VE map. Note that this Scale Bar can be adapted to any other scenarios as you can read in the comments of this project.




Hi Val, 
  
 Thank you very much for producing this. Is there any way to have the adornment layer locked so that it doesn’t move when the user pans around the map? I’ve noticed that it moves with the map and then snaps back into place. 
  
 Best regards, 
  
 Gary

That’s a Web specific question. I will ask the Web Team if this is possible. You should have a response on Monday. Thank you.

 


Gary,
Maybe the script below can help you. It's based on the sample that Val has provided, Please set it to the header part:


 <script language="javascript" type="text/javascript">
        var OnMapCreating = function(map) {
            OpenLayers.Map.prototype.moveTo = function(lonlat, zoom, options) {
                if (!options) {
                    options = {};
                }
                if (zoom != null) {
                    zoom = parseFloat(zoom);
                    if (!this.fractionalZoom) {
                        zoom = Math.round(zoom);
                    }
                }
                // dragging is false by default
                var dragging = options.dragging;
                // forceZoomChange is false by default
                var forceZoomChange = options.forceZoomChange;
                // noEvent is false by default
                var noEvent = options.noEvent;

                if (this.panTween && options.caller == "setCenter") {
                    this.panTween.stop();
                }

                if (!this.center && !this.isValidLonLat(lonlat)) {
                    lonlat = this.maxExtent.getCenterLonLat();
                }

                if (this.restrictedExtent != null) {
                    // In 3.0, decide if we want to change interpretation of maxExtent.
                    if (lonlat == null) {
                        lonlat = this.getCenter();
                    }
                    if (zoom == null) {
                        zoom = this.getZoom();
                    }
                    var resolution = this.getResolutionForZoom(zoom);
                    var extent = this.calculateBounds(lonlat, resolution);
                    if (!this.restrictedExtent.containsBounds(extent)) {
                        var maxCenter = this.restrictedExtent.getCenterLonLat();
                        if (extent.getWidth() > this.restrictedExtent.getWidth()) {
                            lonlat = new OpenLayers.LonLat(maxCenter.lon, lonlat.lat);
                        } else if (extent.left < this.restrictedExtent.left) {
                            lonlat = lonlat.add(this.restrictedExtent.left -
                                                    extent.left, 0);
                        } else if (extent.right > this.restrictedExtent.right) {
                            lonlat = lonlat.add(this.restrictedExtent.right -
                                                    extent.right, 0);
                        }
                        if (extent.getHeight() > this.restrictedExtent.getHeight()) {
                            lonlat = new OpenLayers.LonLat(lonlat.lon, maxCenter.lat);
                        } else if (extent.bottom < this.restrictedExtent.bottom) {
                            lonlat = lonlat.add(0, this.restrictedExtent.bottom -
                                                    extent.bottom);
                        }
                        else if (extent.top > this.restrictedExtent.top) {
                            lonlat = lonlat.add(0, this.restrictedExtent.top -
                                                    extent.top);
                        }
                    }
                }

                var zoomChanged = forceZoomChange || (
                                        (this.isValidZoomLevel(zoom)) &&
                                        (zoom != this.getZoom()));

                var centerChanged = (this.isValidLonLat(lonlat)) &&
                                        (!lonlat.equals(this.center));


                // if neither center nor zoom will change, no need to do anything
                if (zoomChanged || centerChanged || !dragging) {

                    if (!this.dragging && !noEvent) {
                        this.events.triggerEvent("movestart");
                    }

                    if (centerChanged) {
                        //                        if ((!zoomChanged) && (this.center)) {
                        // if zoom hasnt changed, just slide layerContainer
                        //  (must be done before setting this.center to new value)
                        //                            this.centerLayerContainer(lonlat);
                        //                        }
                        this.center = lonlat.clone();
                    }

                    // (re)set the layerContainerDiv's location
                    if ((zoomChanged) || (this.layerContainerOrigin == null)) {
                        this.layerContainerOrigin = this.center.clone();
                        this.layerContainerDiv.style.left = "0px";
                        this.layerContainerDiv.style.top = "0px";
                    }

                    if (zoomChanged) {
                        this.zoom = zoom;
                        this.resolution = this.getResolutionForZoom(zoom);
                        // zoom level has changed, increment viewRequestID.
                        this.viewRequestID++;
                    }

                    var bounds = this.getExtent();

                    //send the move call to the baselayer and all the overlays    

                    if (this.baseLayer.visibility) {
                        this.baseLayer.moveTo(bounds, zoomChanged, dragging);
                        if (dragging) {
                            this.baseLayer.events.triggerEvent("move");
                        } else {
                            this.baseLayer.events.triggerEvent("moveend",
                                    { "zoomChanged": zoomChanged }
                                );
                        }
                    }

                    bounds = this.baseLayer.getExtent();

                    for (var i = 0, len = this.layers.length; i < len; i++) {
                        var layer = this.layers[i];
                        if (layer !== this.baseLayer && !layer.isBaseLayer) {
                            var inRange = layer.calculateInRange();
                            if (layer.inRange != inRange) {
                                // the inRange property has changed. If the layer is
                                // no longer in range, we turn it off right away. If
                                // the layer is no longer out of range, the moveTo
                                // call below will turn on the layer.
                                layer.inRange = inRange;
                                if (!inRange) {
                                    layer.display(false);
                                }
                                this.events.triggerEvent("changelayer", {
                                    layer: layer, property: "visibility"
                                });
                            }
                            if (inRange && layer.visibility) {
                                layer.moveTo(bounds, zoomChanged, dragging);
                                if (dragging) {
                                    layer.events.triggerEvent("move");
                                } else {
                                    layer.events.triggerEvent("moveend",
                                            { "zoomChanged": zoomChanged }
                                        );
                                }
                            }
                        }
                    }

                    if (zoomChanged) {
                        //redraw popups
                        for (var i = 0, len = this.popups.length; i < len; i++) {
                            this.popups[i].updatePosition();
                        }
                    }

                    this.events.triggerEvent("move");

                    if (zoomChanged) { this.events.triggerEvent("zoomend"); }
                }

                // even if nothing was done, we want to notify of this
                if (!dragging && !noEvent) {
                    this.events.triggerEvent("moveend");
                }

                // Store the map dragging state for later use
                this.dragging = !!dragging;

            }
        }
    </script>


 
Thanks,
Johnny


Posted By Johnny on 08-15-2010 09:21 PM


 


Gary,
Maybe the script below can help you. It’s based on the sample that Val has provided, Please set it to the header part:




 
Thanks,
Johnny



Thanks for the script. It’s of great help!



Beast regards.

Bar code create barcode.

Hi, East, 
  
 Any questions, please let us know. 
  
 Thanks, 
  
 Troy