Setting coordinate system

Topics: General Topics
Jan 17, 2007 at 12:24 PM
Hi,

I have figured out how to create a coordinate system for British National Grid based on:

PROJCS["British National Grid (ORD SURV GB)",
GEOGCS["unnamed",DATUM["D_OSGB_1936",
SPHEROID"Airy - 1848",6377563,299.319997677743],
PRIMEM"Greenwich",0,
UNIT"degree",0.0174532925199433],
PROJECTION"Transverse_Mercator",
PARAMETER"latitude_of_origin",49,
PARAMETER"central_meridian",-2,
PARAMETER"scale_factor",0.9996012717,
PARAMETER"false_easting",400000,
PARAMETER"false_northing",-100000,
UNIT"METER",1]

which I got from a PRJ file. The problem I have now is that I can't figure out how to make my map use the above as the basis for its coordinates. For example, if I try to set the centre point of the map to somewhere in the UK using the BNG coordinates, I end up with a blank white image. If I zoom to full extent though, I get an image of the UK that looks right (correct projection).

I also have problems where the zoom level is reported incorrectly, but I think this is linked to the above problem, and if I can sort that out then it should be reported correctly. For example, an 800x600 image of the UK is reported by SharpMap as being 1:1614690, which is very much wrong!

Any ideas?

Cheers,

Dylan
Coordinator
Jan 17, 2007 at 9:44 PM
Hi,

What data source are using?

BR
/Christian
Jan 18, 2007 at 10:45 AM
I've got several layers from SharpMap.Data.Providers.ShapeFile and another layer of data from SharpMap.Data.Providers.MsSqlSpatial. It doesn't appear to make any difference whether I use both providers at the same, or just use one type at a time. The scale is still incorrect, and the coordinate system still doesn't seem to be BNG.

Incidentally, if I use the Map.ZoomToBox() method along with coordinates specified in BNG, it does in fact zoom to the correct location! It's only when I use the Map.Center property to set the location, followed by the Map.Zoom property to set the scale that it doesn't work as expected.
Jan 18, 2007 at 11:15 AM
Hmm... Having done some more tests, it appears that when I set the center property of the map object it does centre on the correct location, but the zoom property is still incorrect. For example, if I set the zoom to be 250000 it certainly isn't producing a map at 1:250000! It looks (and this is only a visual comparison) that the image produced is at 1:~1000000, ie. 4 times smaller than it should be.

I'm getting very confused now :s
Mar 2, 2007 at 4:59 PM
Edited Mar 2, 2007 at 5:01 PM
Hi Dylan,

It looks like the map's Zoom property is actually the width of the displayed map in world units and not a zoom factor! If you look at the map.Envelope.Width value, you'll find it's the same as the map.Zoom value.

To get the scale the map is displayed at, do something like:

double dScale;
double dWindowWidthInmetres;

dWindowWidthInmetres = (Convert.ToDouble(map.Size.Width) / mapImage1.CreateGraphics().DpiX) * 0.0254;
dScale = map.Zoom / dWindowWidthInmetres;

To set the scale the map is displayed at, do something like:

double dScale = Convert.ToDouble(txtScale.Text);
double dWindowWidthInmetres;

// The map's zoom factor is actually the width of the map in world units
// We have to convert the scale we want into the zoom value
dWindowWidthInmetres = (Convert.ToDouble(map.Size.Width) / mapImage1.CreateGraphics().DpiX) * 0.0254;

map.Zoom = dScale * dWindowWidthInmetres;
mapImage1.Refresh();


So if you wanted a 1:10,000 scale map, just set dScale = 10000 in the code snippet above.

Note that a Layer's MinVisible and MaxVisible values are checked against the map's Zoom value, so if you want your layers to turn on and off at various scales, it won't work as you expect.

Hope this helps

Bill
Coordinator
Mar 3, 2007 at 9:28 PM
The Zoom property is the width of the envelope - it is defined as the number of world units in the current view. There is no MapScale property. There probably should be... I'll add it as a Work Item.

The pixel width in world units is stored in the PixelWidth property. So if the world units are feet or meters (or whatever), then you just need to multiply by the DPI and convert to whatever other measurement you want (besides the Inch in DPI).

Also, recognize that all DPI measurements are in "notional inches" which means that the inch may be bigger or smaller than an actual inch depending on your monitor.

Taking this all into account, your calculations can be simplified like this:
const double MetersPerInch = 0.0254;
const double CentimetersPerInch = 2.54;
const double FeetPerInch = 0.08333;
 
// Make this a class-level field, so you don't have to recalculate it
double _screenDpi;
 
// Do this once at load time, or you could kill your system 
// by leaking GDI handles all over
using (Graphics g = mapImage1.CreateGraphics())
   _screenDpi = g.DpiX;

To get:
double worldUnitsPerViewInch = map.PixelWidth * _screenDpi;
 
// Now you can convert to your heart's content
double worldUnitsPerViewMeter = worldUnitsPerViewInch * MetersPerInch;
double worldUnitsPerViewCentimeter = worldUnitsPerViewInch * CentimetersPerInch;
double worldUnitsPerViewFoot = worldUnitsPerViewInch * FeetPerInch;

To set:
double scaleMeters = GetRequestedScaleInMeters();
double scaleInches = scaleMeters / MetersPerInch;
double scalePerPixel = scaleInches / _screenDpi;
map.Zoom = mapImage1.Width * scalePerPixel;

Using these calculations, you can also supply correct values to Layer.MinVisible and Layer.MaxVisible to have them active at desired scaling.
Coordinator
Mar 3, 2007 at 9:30 PM
This discussion has been copied to Work Item 8643. You may wish to continue further discussion there.
Mar 4, 2007 at 1:51 AM
See my comment to the work item.
Mar 5, 2007 at 10:53 AM
I'm not really convinced that the way the Zoom property value is calculated is the best way of doing it. I don't think it should be based on the actual width of the produced image, but rather a fixed measurement. So for example, instead of returning a value of 1000m for my image that is 640px wide, it would return a more sensible value of 156.25m as being 100px. This would make life more simple when it comes to calculating distances.

I think it would be a good idea if you were able to set a property of the map object that allows you to specify exactly what the Zoom property should be based on, so for example:

map.ZoomCalculations = 100;

would base the property on 100px width. Of course, there would have to be a fall back for when this property isn't set, and also to allow for backward compatibility. I would suggest here that if the property isn't set, then the calculations are based on the width of the map image as they are now.

This would make it much simpler to work with maps when you don't know what the width of the image is going to be, as currently if you specify that a layer should only be displayed when the zoom is of a certain value it depends as much on the width of the image as it does the actual scale of the map. If you knew that the Zoom was based on a fixed width, this would negate that fact.
Coordinator
Mar 6, 2007 at 8:56 PM
How is this different than the MapScale property introduced in Add MapScale property? The idea would be to get or set a scale depending on your units. This would in turn affect the zoom.

However, Odegaard's point, if I understand it, is well taken. Doing this kind of thing on the screen doesn't make much sense (unless you are trying to scale the map relative to something of known size - so that they are both equally distored by screen geometry and physical characteristics). Zoom, on a screen, is much more useful. How do you use Google Earth, for example... set a scale and move around? No - you zoom in and out and move around. The scale isn't as meaningful in this usage.
Mar 7, 2007 at 11:14 AM
It's not a great deal different, but the main difference is that rather than setting a map scale (eg 1:10000) you be setting a property that gives context to the existing zoom property. At the moment, the zoom property is abstract to say the least as it is based upon the width of the map, which could be anything.

It also makes it difficult to set max and min display values when the size of the map is dynamic. For example, a 400px-wide map with a zoom of 100 would should 100m over 400px, whereas a map of 800px wide with the same zoom would be showing half as much data per pixel, so to get the same detail you'd have to use a zoom of 200.

If you were to base the zoom calculation on a fixed unit, say 100px, then you'd always know that you were showing X metres over 100px of map.

Also, as a MapScale property would not affect the min and max display for layers, it would probably require extra work to get it working in that way. Whereas the zoom property is what currently affects this, so changing the way in which zoom is calculated would instantly impact upon this - ie. if I wanted to only show a layer when there are 50m over 100px then I would be able to set the MaxZoom to 50 and know it will always work regardless.

I'm probably not explaining myself properly, so hopefully you can see what I am getting at?