We're moving from OpenLayers to Google Maps, and so we're also having to change from using 27700 to 3857 for simplicity. OpenLayers handled this easily, however Google Maps seems to be a bit more complicated, and for whatever reason the rows aren't matching up correctly.

If I add the layer into QGIS, it loads correctly, so it looks like the layer is set up correctly on geoserver. Bear in mind that the data is stored as 27700, but being displayed as 3857 (will likely change this later on)

Looking at a similar place on the map on QGIS and our Google Maps implementation, the columns looks correct (31618 on QGIS vs 31613 on Google Maps), however the row is completely different (7297 on QGIS vs 22250 on google maps)

I'm using the pre-generated GoogleCRS84Quad gridset on geoserver

Where am I going wrong?


var BASE_URL = `https://localhost:8080/geoserver/gwc/service/wmts/rest/wmts:all_freehold_titles/

export default class TitleLayer implements LayerOptions {
  protected layer: LayerType

  alt: string | null
  maxZoom: number
  minZoom: number
  name: string | null
  projection: google.maps.Projection | null
  radius: number
  tileSize: google.maps.Size | null
  mapHandler: MapHandler

  getTile(tileCoord: google.maps.Point, zoom: number, ownerDocument: Document): Element | null {
    const url = BASE_URL.replace("{x}", tileCoord.x.toString())
      .replace("{y}", tileCoord.y.toString())
      .replace("{z}", zoom.toString())
      .replaceAll(" ", "")

    const img = ownerDocument.createElement("img")
    img.style.width = this.tileSize?.width + "px"
    img.style.height = this.tileSize?.height + "px"

    img.setAttribute("src", url)
    return img

  releaseTile(tile: Element | null): void {}

  constructor(options) {
    this.alt = options.alt || "titles"
    this.minZoom = options.minZoom || 1
    this.maxZoom = options.maxZoom || 22
    this.name = options.name || "titles"
    this.projection = options.projection || null
    this.radius = options.radius || 6378137 // Radius of the earth
    this.tileSize = options.tileSize
    this.mapHandler = options.mapHandler

Layer CRS config

enter image description here

Google maps generated url:


QGIS generated URL:


  • your lat/lon bounding box looks wrong to me - I doubt uk data goes to the poles and pacific
    – Ian Turton
    Commented Jan 24, 2023 at 9:41
  • I've replaced the bounding box with the one from the OSGB gridset we have on our geoserver, but still the same thing happens. Also bear in mind that it works on QGIS, and the columns are correct, so I don't think the bounding box would be the issue
    – Alistair R
    Commented Jan 24, 2023 at 9:51

1 Answer 1


I found the answer by accident eventually. I needed to use the EPSG:900913 (google) gridset, and it all worked no problems

Even though this is not an official CRS, and it's only slightly different to EPSG:3857 (which is the official version of 900913), it still works fine with google maps.

For the bounding box of the layer source and the declared CRS, I set them as EPSG:27700 still with the bounds computed from the SRS bounds

Correct grid set

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.