Step 4: Make the GeoJSON

In this step, we'll generate a GeoJSON from the CSV table data. Later we call the Update map endpoint with the generated GeoJSON.

GeoJSON on MapHub

Every map on MapHub is stored as a GeoJSON document. All map data: points, polygons, etc. are stored in this single document. To understand the structure of a MapHub map, the best thing is to download a map you've created as GeoJSON.

You can do this by opening one of your maps on MapHub.net and clicking on the "Download" button in the right side panel and then selecting "MapHub GeoJSON".

For example, the finished map of this tutorial has the following GeoJSON:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "id": 515312282,
      "geometry": {
        "type": "Point",
        "coordinates": [-0.128018, 51.519294]
      },
      "properties": {
        "title": "British Museum",
        "description": "The world-famous British Museum exhibits the works of man from prehistoric to modern times, from around the world. Highlights include the Rosetta Stone, the Parthenon sculptures and the mummies in the Ancient Egypt collection.",
        "url": "https://www.britishmuseum.org/",
        "marker-symbol": "museum",
        "image": {
          "id": "pwasvdaus75tnn3w",
          "w": 640,
          "h": 360,
          "tip_color": "#bdbbcb",
          "avg_color": "#8895aa"
        }
      }
    },
    {
      "type": "Feature",
      "id": 521910492,
      "geometry": {
        "type": "Point",
        "coordinates": [-0.128374, 51.508884]
      },
      "properties": {
        "title": "National Gallery",
        "description": "The crowning glory of Trafalgar Square, London's National Gallery is a vast space filled with western European paintings from the 13th to the 19th centuries. Find works by masters such as Van Gogh, da Vinci, Botticelli, Constable, Renoir, Titian and Stubbs.",
        "marker-symbol": "star",
        "image": {
          "id": "pr1xhpavd1atxs5l",
          "w": 640,
          "h": 360,
          "tip_color": "#8d857b",
          "avg_color": "#8c8c8e"
        }
      }
    },
    {
      "type": "Feature",
      "id": 1233836134,
      "geometry": {
        "type": "Point",
        "coordinates": [-0.099342, 51.507429]
      },
      "properties": {
        "title": "Tate Modern",
        "description": "Sitting grandly on the banks of the Thames is Tate Modern, Britain's national museum of modern and contemporary art. Its unique shape is due to it previously being a power station. The gallery's restaurants offer fabulous views across the city.",
        "url": "https://www.tate.org.uk/visit/tate-modern",
        "marker-symbol": "museum",
        "image": {
          "id": "yibgsqadfifyaste",
          "w": 640,
          "h": 360,
          "tip_color": "#8493ab",
          "avg_color": "#9c9ea1"
        }
      }
    },
    {
      "type": "Feature",
      "id": 3383498660,
      "geometry": {
        "type": "Point",
        "coordinates": [-0.171637, 51.496884]
      },
      "properties": {
        "title": "Victoria and Albert Museum",
        "description": "The V&A celebrates art and design with 3,000 years' worth of amazing artefacts from around the world. A real treasure trove of goodies, you never know what you'll discover next: furniture, paintings, sculpture, metalwork and textiles; the list goes on.",
        "url": "https://www.vam.ac.uk/",
        "marker_id": "bvd5ykfilp7ekxyh",
        "image": {
          "id": "ikxqpaqycbrpannw",
          "w": 640,
          "h": 360,
          "tip_color": "#d1b896",
          "avg_color": "#725d42"
        }
      }
    }
  ],
  "groups": []
}

Note: Some keys like markers, properties and export are not displayed here, as they are not important for this tutorial.

The id fields are optional, you can leave them out and they'll be automatically created.

Creating the GeoJSON from the table data

The following function is the core of this tutorial, it creates a GeoJSON from the table data we have. It also reads information from the image and marker JSONs and inserts it into the GeoJSON.

def create_geojson_from_csv(csv_file):
    assert csv_file.is_file()

    features = []

    with open(csv_file, newline='') as f:
        # auto-detect the CSV dialect
        dialect = csv.Sniffer().sniff(f.read())
        f.seek(0)

        # use DictReader which gives a dict for each row
        reader = csv.DictReader(f, dialect=dialect)

        # for each row in the CSV file
        for row in reader:
            row = dict(row)

            lat = float(row['latitude'])
            lon = float(row['longitude'])

            # basic properties
            properties = {
                "title": row['title'],
                "description": row['description'],
                "url": row['url'],
            }

            # if we have an icon from the default set, use it
            if row['icon_default']:
                properties['marker-symbol'] = row['icon_default']

            # if we have a custom icon, use the uploaded marker
            if row['icon_custom']:
                marker_info = get_image_marker_info('marker', row['icon_custom'])
                if marker_info:
                    properties['marker_id'] = marker_info['marker_id']

            # if we have an image, use the uploaded image
            if row['image']:
                image_info = get_image_marker_info('image', row['image'])
                if image_info:
                    properties['image'] = {
                        'id': image_info['image_id'],
                        'w': image_info['width'],
                        'h': image_info['height'],
                        'tip_color': image_info['tip_color'],
                        'avg_color': image_info['avg_color'],
                    }

            # create the GeoJSON for the item
            feature = {
                "type": "Feature",
                "geometry": {"type": "Point", "coordinates": [lon, lat]},
                "properties": properties,
            }
            features.append(feature)

    geojson = {
        "type": "FeatureCollection",
        "features": features,
    }

    return geojson

The core of get_image_marker_info helper function:

def get_image_marker_info(kind, file_name):
    assert kind in {'image', 'marker'}

    if kind == 'image':
        file_path = IMAGES_DIR / file_name
    else:
        file_path = MARKERS_DIR / file_name

    info_json = file_path.parent / f'{file_path.stem}.json'

    with open(info_json) as f:
        return json.load(f)

In the next step, we will call the Update map endpoint with the generated GeoJSON.