Open Layers - Add custom projection

post on

3 min read

This article is a part of series on OpenLayers. The code of every new feature will be based on code from previous article.

Imagine that out task is to add custom projection “EPSG:2154” to use on our map for France terrirory. How can we do it?
Well, first of all for custom projections we have to install library proj4.

npm i proj4
npm i proj4

Now there is 2 ways of adding custom projection on your map. It’s important to know that both ways don’t include extent of projection. If you have to access projection extent, you should copy it manually from epsg.io website (projected bounds)

Adding custom projection : 1st option

The first option is to using OpenLayers fromEPSGCode method from ‘ol/proj/proj4’ package.
You should first register proj4 before using this function, then calling fromEPSGCode function.
Notice how we transform onMounted hook in async function, as fromEPSGCode is a promise:
“This function fetches the projection definition from the epsg.io website, registers this definition for use with proj4, and returns a configured projection.”

App.vue
<script>
...
import { fromEPSGCode, register } from "ol/proj/proj4"
import proj4 from "proj4"
import { LAMBERT_PROJECTION } from "./application/consts"
 
...
onMounted(async () => {
  register(proj4)
  await fromEPSGCode(LAMBERT_PROJECTION)
  ...
})
 
</script>
App.vue
<script>
...
import { fromEPSGCode, register } from "ol/proj/proj4"
import proj4 from "proj4"
import { LAMBERT_PROJECTION } from "./application/consts"
 
...
onMounted(async () => {
  register(proj4)
  await fromEPSGCode(LAMBERT_PROJECTION)
  ...
})
 
</script>

Now let’s add new projection in consts.ts and default.ts config files.

consts.ts
...
export const LAMBERT_PROJECTION = "EPSG:2154"  // <-- new projection code
export const DEFAULT_PROJECTION = "EPSG:3857"
export const WGS84_PROJECTION = "EPSG:4326"    
export const PROJECTIONS = [                   // <-- add new projection code in array
  LAMBERT_PROJECTION,
  DEFAULT_PROJECTION,
  WGS84_PROJECTION,
] as const
consts.ts
...
export const LAMBERT_PROJECTION = "EPSG:2154"  // <-- new projection code
export const DEFAULT_PROJECTION = "EPSG:3857"
export const WGS84_PROJECTION = "EPSG:4326"    
export const PROJECTIONS = [                   // <-- add new projection code in array
  LAMBERT_PROJECTION,
  DEFAULT_PROJECTION,
  WGS84_PROJECTION,
] as const
const getProjectionConfig = (): ProjectionConfig => {
  return {
    [DEFAULT_PROJECTION]: {
      center: fromLonLat(VIEW_CENTER),
      resolutions: RESOLUTIONS_GRID_METERS,
    },
    [WGS84_PROJECTION]: {
      center: VIEW_CENTER,
      resolutions: RESOLUTION_GRID_DEGREES,
    },
    [LAMBERT_PROJECTION]: {    // <-- update projection config
      center: fromLonLat(VIEW_CENTER, LAMBERT_PROJECTION),
      resolutions: RESOLUTIONS_GRID_METERS,
    },
  }
}
const getProjectionConfig = (): ProjectionConfig => {
  return {
    [DEFAULT_PROJECTION]: {
      center: fromLonLat(VIEW_CENTER),
      resolutions: RESOLUTIONS_GRID_METERS,
    },
    [WGS84_PROJECTION]: {
      center: VIEW_CENTER,
      resolutions: RESOLUTION_GRID_DEGREES,
    },
    [LAMBERT_PROJECTION]: {    // <-- update projection config
      center: fromLonLat(VIEW_CENTER, LAMBERT_PROJECTION),
      resolutions: RESOLUTIONS_GRID_METERS,
    },
  }
}

That’s all : new projection should appear in options list of MapBar projection select. Then you can switch between these projections with ease.

Adding custom projection : 2nd option

The second option consist of copy proj4 definition from epsg.io site.
Go to the bottom of page for your projection and select :
Export -> Proj4js -> Copy text
It will be something like

proj4.defs("EPSG:2154","+proj=lcc +lat_0=46.5 +lon_0=3 +lat_1=49 +lat_2=44 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs");

Then in onMounted hook instead of calling fromEPSGCode Promise, we do:

App.vue
<script>
...
import { fromEPSGCode, register } from "ol/proj/proj4"
import proj4 from "proj4"
 
...
onMounted(() => {
  proj4.defs(
    "EPSG:2154",
    "+proj=lcc +lat_0=46.5 +lon_0=3 +lat_1=49 +lat_2=44 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs"
  )
  register(proj4)
  ...
})
 
</script>
App.vue
<script>
...
import { fromEPSGCode, register } from "ol/proj/proj4"
import proj4 from "proj4"
 
...
onMounted(() => {
  proj4.defs(
    "EPSG:2154",
    "+proj=lcc +lat_0=46.5 +lon_0=3 +lat_1=49 +lat_2=44 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs"
  )
  register(proj4)
  ...
})
 
</script>

And that’s all, it should work as before. The difference is that you hard code custom projection definition instead of make network call.
To make code a little better, let’s extract definition in consts file:

consts.ts
...
// definitions
export const LAMBERT_PROJECTION_DEFINITION =
  "+proj=lcc +lat_0=46.5 +lon_0=3 +lat_1=49 +lat_2=44 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs"
 
consts.ts
...
// definitions
export const LAMBERT_PROJECTION_DEFINITION =
  "+proj=lcc +lat_0=46.5 +lon_0=3 +lat_1=49 +lat_2=44 +x_0=700000 +y_0=6600000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs"
 

Then in App.vue

App.vue
<script>
...
onMounted(() => {
  proj4.defs(LAMBERT_PROJECTION, LAMBERT_PROJECTION_DEFINITION)
  register(proj4)
  ...
})
 
</script>
App.vue
<script>
...
onMounted(() => {
  proj4.defs(LAMBERT_PROJECTION, LAMBERT_PROJECTION_DEFINITION)
  register(proj4)
  ...
})
 
</script>

  1. #GIS
  2. #OpenLayers