ThinkGeo.com    |     Documentation    |     Premium Support

What properties need to be set when changing projections?

I’m noticing that layers seem to disappear when switching between projections sometimes. shapefile.zip (2.4 MB) Could I get a list of what properties on the map/overlay/layers that I should be modifying when changing projections?
I’m on v13.1.2.

This is what I do when changing projections. simply looping through layers and changing the external projection or creating a new projection converter if it doesn’t already have one:

myMap.MapUnit = newProjection.GetUnit();

// this is in a loop of an overlay's layers
if (layer is FeatureLayer featureLayer)
{
    if (featureLayer.FeatureSource.ProjectionConverter == null)
    {
        featureLayer.FeatureSource.ProjectionConverter = new ProjectionConverter(4326, e.Projection.Srid);
    }
    else
    {
        featureLayer.FeatureSource.ProjectionConverter.ExternalProjection = newProjection;
    }
}

Do I have to worry about changing the scale, current extent, wrapping properties, etc.? Not sure what I’m missing here. I see something similar in the How Do I sample -> Project the World. Select MGA Zone 55 and zoom out a little bit and the layer disappears. Not sure if it’s the same issue.

I attached a zip file of a shapefile containing countries. This is the main layer I am testing with. the layers mainly seem to disappear when going to 4326, even if the original data is in 4326.

Edit: If I have the map in decimal degrees, and the layer’s internal/external projection are both

, then the layer does not show up. Once I swap the external projection to mercator it does work. Is there a reason for this I’m not thinking of? It also seems like the bounding box of the layer is way too big. It should be within -180,90,180,-90 but I am seeing values in the 200s/300s.

Hi Dan,

When switching projections, there are three main things to take care of:

  1. MapUnit.
    Make sure the map’s unit matches the new projection. In your code, myMap.MapUnit = newProjection.GetUnit(); is correct — that ensures consistency between the projection and map unit. However, also handle the case where newProjection is null (for example, when reverting to its native coordinate system). In that case, explicitly set it to GeographyUnit.DecimalDegree if your data is natively in latitude/longitude.

  2. CurrentExtent
    When switching from Projection A to Projection B, you also need to reproject the current extent or center point to the new coordinate system. In v14.3 and later, it’s recommended to reproject the center point, for example: MapView.CenterPoint = ProjectionConverter.Convert(fromSrid, toSrid, MapView.CenterPoint);. In v13.1.2 , you can reproject the entire extent instead of just the center.

  3. Layer ProjectionConverter Refresh
    Be aware that the code: featureLayer.FeatureSource.ProjectionConverter.ExternalProjection = newProjection; won’t take effect until the projection converter is reopened.
    To apply it immediately, either:

featureLayer.FeatureSource.ProjectionConverter.Close();
featureLayer.FeatureSource.ProjectionConverter.Open();

or simply create a new ProjectionConverter instance instead of modifying the existing one.

Additional notes:

  • Each projection has its own valid region . For example, displaying the entire world using an Australian MGA Zone 55 projection will cause issues. That’s why in the “Project the World” sample, the layer disappears when zooming out under MGA Zone 55 — it’s kind of expected due to the projection’s limited extent.

This combination — consistent MapUnit , correctly reprojected extent or center point, and refreshed ProjectionConverter — should keep your layers visible and correctly aligned when switching projections.

Thanks,
Ben

Hi Ben, thanks for the info! I created a small sample project you can retrieve here https://drive.google.com/file/d/1wDa18uKxJxiPz7it-uy2CwbK1WsQEivy/view?usp=sharing

Looks like there is an issue converting the extent. However, I tried a few different combos with the projection converter and external projection but no luck getting the layer to appear in 4326.

Hi Dan,

I made some changes and made it work. MainWindow.xaml (901 Bytes)

Some notes here:

  1. I use SingleTile for the Overlay, because it had some issue with MultiTile in that version plus SingleTile is more efficient anyway when projection is enabled.
  2. I did the reprojection based on the center point of the current extent, instead of the current extent itself.

Let me know if you still see any issues.

Thanks,
Ben

Hi Ben, looks like just the xaml file was uploaded. Mind trying again?

One other question :slight_smile: If I’m using custom zoom levels for the map, do I need to change anything when projecting the layers? Or I guess more specifically, when changing the MapUnit? Depending on the projection/map unit, the furthest zoomed-in zoomlevel is hit earlier.

Sorry, my bad, here is the cs: MainWindow.xaml.cs (5.6 KB)

In my code, I reproject the center point without changing the current map scale.
In other words, you can continue using the same ZoomLevelSet even after changing the MapUnit , even if the zoom level set is customized.

By the way, in newer versions (v14.1 and later), MapView.ZoomLevelSet has been replaced with Collection<double> MapView.ZoomScales, also You can directly set MapView.CenterPoint, which makes the code more straightforward.

1 Like

Thanks Ben! This helps a lot, I appreciate it. Hoping to upgrade to v14 eventually!

No problem! Just let us know whenever you need any help!

Hi Ben, one more question related to all this. When using the TrackInteractiveOverlay to draw a shape, does the TrackShapeLayer also need to have its projection set? For instance, I am using the InteractionArguments passed into the overriden MouseMoveCore/ClickCore functions which gives me WorldX and WorldY values. Will these values be accurate regardless of the projection as long as the map unit is correct?

An example would be using the track layer to let the user draw a line point by point, while showing the total distance of the line above the line being drawn (like the common measure tool seen in various GIS apps). If I’m calculating the distance using those WorldX/Y, I assume it would be accurate regardless of projection. Hmm, but then if I wanted to put that feature into a different layer, I wouldn’t know the feature’s projection unless it was set on the trackshapelayer??

Something I just tried:

  • MapUnit is currently meters
  • Used trackshapelayer to draw a line (no projection used on this layer)
  • Converted the line feature to 4236 because the layer that I am moving the line to has an internal projection of 4236. I used a projectionConverter with an internal projection equal to the layer’s external projection, and external equal to the layer’s internal projection (which is 4236). The converted line’s vertices look correct (as in, the same location where I drew the line)
  • Added the line feature to the other layer using BeginTransaction/AddFeature/CommitTransaction. Verified the feature is now in the layer, while the InternalFeatures shows the feature in its projected version, and GetAllFeatures shows the feature in its internal version.
  • The feature does NOT show up in the right place on the map though

Here’s the internal/external projection values of the layer’s featureSource.ProjectionConverter:

MapUnit is Meter.

Feature in InternalFeatures: {LINESTRING(-75.9705041058175 38.2928592693863,-75.9631930985656 38.2860449906408)}
Feature in GetAllFeatures: {LINESTRING(-0.000682454649805207 0.000343990607541037,-0.00068238897390964 0.000343929393835026)}

On the map, it shows up in the “GetAllFeatures” version instead of at the spot where it was drawn which matches the InternalFeatures version.

Think I found the issue. In the blog post here https://thinkgeo.com/blog/best-practices-for-adding-features
it says that when using the AddFeature() method, “You simply add the new feature in the same internal projection that the rest of your layer is using”. I take this to mean that, if have a layer whose internal projection is 4326, and I add a feature that is in 4326, I simply add the feature as is.

However, peeking into that AddFeature() method, I see this: image

it’s taking the feature and projecting it to the internal projection. I believe either the blog post or this function should be updated, unless I’m misunderstanding.

Hi Dan,

Thanks for letting us know!

  1. Projection transparency
    The internal ProjectionConverter should be transparent to users. If the map renders in Projection A while the data source is Projection B, all public APIs operate in A and users shouldn’t need to know about B (unless they override core methods). Concretely:

    • Public methods (e.g., GetFeaturesInsideBoundingBox(...)) expect shapes in external Projection A.
    • The corresponding protected virtual Core methods (e.g., GetFeaturesInsideBoundingBoxCore(...)) work with shapes in internal Projection B.
  2. Blog correction
    Good catch—our blog wording was imprecise. AddFeature(...) should receive features in the external projection (not the internal one). We will have it updated.

  3. Measuring distance correctly
    The WorldX/WorldY you see while tracking are correct for the current (external) projection, but using those raw coordinates for distance math is not accurate. Instead you need to create a LineShape from the tracked vertices and call GetLength with the line’s projection:

    // Vertices are in Spherical Mercator (EPSG:3857)
    // This method reproject the line to local UTM and then calculate the length 
    var lengthMeters = line.GetLength(3857, DistanceUnit.Meter, DistanceCalculationMode.LocalizedUtmZone);
    

    For very long lines spanning multiple UTM zones, prefer Haversine (great-circle):

    var lengthMeters = line.GetLength(3857, DistanceUnit.Meter, DistanceCalculationMode.Haversine);
    

I hope that helps.

Thanks,
Ben

1 Like

And the blog has been updated, it’s now “You simply add the new feature in the same external projection that the rest of your layer is using”.

1 Like

Awesome, thanks again Ben!

Sure, no problem! :+1: