Categories
Web Map

Draw beautiful NZ boundary map image and tiles using PHP

I have been researching how to demonstrate some New Zealand statistical data in a geographical map service recently and drew a beautiful all NZ boundary map using PHP. Log here the whole process and I hope it is helpful for you in your project.

Firstly, download the original boundary data from the StatsNZ official website, I chose meshblock 2018 with the WGS 84(EPSG 4326) projection for my graphic.

Secondly, import the data into PostGIS database, you can use the ogr2ogr tool to import it, the reference command is here:

%ogr2ogr -f PostgreSQL "PG:user=postgres password=123456 host=localhost dbname=meshblock_2018_generalised" meshblock-2018-generalised.gpkg

After downloaded, you can access all data from the table “meshblock_2018_generalised” in PostGIS.

Thirdly, we need to get geographical data, all geo data is saved in the geometry field “geom”, we need to convert it to GeoJSON format then we can draw polygons. It is pretty easy to do it with the PostGIS’s internal funciton:

SELECT ST_ASGeoJSON(geom) AS geom FROM meshblock_2018_generalised

Finally, to draw these polygons in the png file, we need to

  • Convert every point from lon&lat coordinates(lat, lng) to point position(px, py) (Refer ESPG3857 Web Mecator)
  • Set the boundary axis start point position(sx, sy), end point position(ex, ey), image width, and image height
  • Calculate every point’s real position to the image, use the point position(px, py) minus start point(sx, sy), then you can get the point’s position in the image.
  • Draw the polygons using PHP function imagepolygon
  • Combine with the image width and height

This is the main part of the source code in PHP7+Laravel8

// Get all meshblock record from PostGIS
$cbs = Meshblock2018Generalised::getByBounds([[-180, -90], [180, 90]], true);


$tile_pixels = tile_to_pixel($zoom, [$x, $y], $image_size);

$coordinate_points = [];

foreach($cbs as $cb) {
     $geo_json = json_decode($cb['raw_geo_json']);
     $color = color_hex_to_rgb($cb['color']);

     foreach ($geo_json->coordinates as $k => $v) {
         $arr = [];
         foreach ($v[0] as $point) {
             $arr[] = (lon_to_pixel($zoom, $point[0], $image_size) - $tile_pixels[0] - 50 * $image_size / 256);
             $arr[] = (lat_to_pixel($zoom, $point[1], $image_size) - $tile_pixels[1] - 120 * $image_size / 256);
         }
         $coordinate_points[] = ['color' => $color, 'points' => $arr];
    }
}

// Create a transparent png image
$image = imagecreatetruecolor($image_size, $image_size);
$black = imagecolorallocate($image, 0, 0, 0);
imagecolortransparent($image, $black);

// Allocate a color for the polygon
$col_poly = imagecolorallocate($image, 88, 88, 88);

// Draw the polygon
foreach( $coordinate_points as $item){
    imagepolygon($image, $item['points'],count($item['points']) / 2,  $col_poly);
}

// Output the picture to the browser
header('Content-type: image/png');

imagepng($image);
imagedestroy($image);
exit;

The below graphic is generated in the border mode, I recommend you to open them in the new target, you will feel how beautiful these lines.

Part of New Zealand Meshblock Boundary
New Zealand Meshblock Boundary 4096

You also can fill it with colors like below or divide it into tiles per your requirement.

Welcome to leave a comment if you have any queries.

Categories
Web Map

Use d3js to show the map of New Zealand regional council boundaries

The article introduces how to use d3js showing the map of New Zealand regional council boundaries.

You can get the geometry data of NZ regional council from the NZ official website: https://datafinder.stats.govt.nz/layer/92197-meshblock-2018-generalised, remember to download the WGS84(EPSG:4326) projection format.

I downloaded the original data from the above URL and converted it to the Topo-JSON type as below.

Move the mouse to the map to get the regional councils’ basic info:

The source code is here:

<script src="https://d3js.org/d3.v6.min.js"></script>
<script src="https://unpkg.com/topojson-client@3"></script>
<div id="map-nz-regional-council"></div>
<script>


        window.addEventListener('load', async () => {
            await init_map();
        })

        async function init_map(){
            let width = 960
                , height = 960
                , centered;

            const file_name_topojson = '/wp-content/uploads/2021/03/regional_council_2018_generalised.topojson.json';
            const topo_json = await d3.json(file_name_topojson);

            const zoom = d3.zoom()
                .scaleExtent([1, 4])
                .on('zoom', zoomed);

            function zoomed(event) {
                const {transform} = event;
                g.attr("transform", transform);
                g.attr("stroke-width", 1 / transform.k);
            }

            const projection =
                    d3.geoMercator()
                    .scale(2000)
                    .center([176, -38]);

            const path = d3.geoPath(projection);

            const svg = d3.select('#map-nz-regional-council').append('svg')
                .attr('width', width)
                .attr('height', height)
                .attr('fill', '#fff');

            svg.append('rect')
                .attr('class', 'background')
                .attr('width', width)
                .attr('height', height)
                .on('click', clicked);

            svg.call(zoom);

            const g = svg.append('g');

            const textLayer = g.append('g');

            const mapLayer = g.append('g')
                .classed('map-layer', true);

            // const features = geo_json.features;

            // Draw each provinces as a path
            mapLayer.selectAll('path')
                // .data(geo_json.features)
                .data(topojson.feature(topo_json, topo_json.objects['regional_council_2018_generalised']).features)
                .enter()
                .append('path')
                .attr('d', path)
                .attr('vector-effect', 'non-scaling-stroke')
                .style('stroke', '#000000')
                // .style('fill', fillFn)
                .on('mouseover', mouseover)
                .on('mouseout', mouseout)
                .on('click', clicked)
                ;

            function clicked(d){
                const pointer = d3.pointer(d);
                const res = projection(pointer);
                showInfo(d3.select(this).data());

            }

            function showInfo(data){
                textLayer.selectAll('text').remove();
                if(data && data[0]){
                    Object.keys(data[0]['properties']).map((k, i) => {
                        textLayer.append('text')
                            .attr('x', 0)
                            .attr('y', i * 14)
                            .attr('dy', '1.2em')
                            .style('fill', '#000')
                            .text( k + " => " + data[0]['properties'][k] );
                    });
                }
            }

            function mouseover(d){
                // Highlight hovered province
                d3.select(this).style('fill', 'orange');
                showInfo(d3.select(this).data());
            }

            function mouseout(d){
                // Reset province color
                mapLayer.selectAll('path')
                    .style('fill', function(d){return centered && d===centered ? '#D5708B' : '#ffffff';});
            }

        }

</script>
Categories
Web Map

Build your self-host world map – Download data and render tiles on your mac

This article demonstrates how to download and serve a city’s map on your Macbook.

1. Download openstreetmap data

Firstly we need to get the OpenStreetMap data for rendering tiles, you can download it from various places, refer “LearnOSM – Getting OSM Data” to get the specific data you want. I downloaded Auckland city data from BBBike.org

2. If there is no Docker installed on your Mac, download and install it.

3. Setting up the tiles server:

1). Create new volume in Docker:

% docker volume create openstreetmap-data

2). Use openstreetmap-tile-server to build the server, start importing the “.pbf” file into PostgreSQL by running a container and mounting the file as /data.osm.pbf. Remember to use the physical file path:

% docker run -v /Users/ukalpa/Downloads/Auckland.osm.pbf:/data.osm.pbf -v openstreetmap-data:/var/lib/postgresql/12/main overv/openstreetmap-tile-server import

3). Running the server

% docker run -p 8301:80 -v openstreetmap-data:/var/lib/postgresql/12/main -d overv/openstreetmap-tile-server run

Now, the tiles server has been installed successfully, access http://localhost:8301 and you will see the Auckland map details:

Auckland City Summary
Auckland Downtown
Queen Street

For more details about openstreetmap-tile-server, please refer: https://github.com/Overv/openstreetmap-tile-server

Categories
Web Map

Build your self-host world map – Install mapnik & python-mapnik on MacOS

Mapnik is a mapping toolkit for developing mapping applications. it can generate map tiles with specific requirements based on the data in PostGIS generated by OSM. This article introduces how to install it and the python library “python-mapnik” for Python3 on Mac OS.

First, make sure you have installed homebrew on your Macbook. And it is updated:

%brew update

Then, install mapnik:

%brew install mapnik

After installed, Execute “brew info mapnik” to check the installed details, you can see the version 3.1.0 has been installed successfully as below:

Now, let us start to install “python-mapnik”:

First, clone the “python-mapnik” source code to the local directory and switch its version to “v3.0.x”, the reason for switching the version is because the master branch is not compatible with the previous installed mapnik’s version.

%git clone https://github.com/mapnik/python-mapnik.git
%cd python-mapnik
%git checkout v3.0.x

You may encounter the issue: “library not found boost_python” when installing python-mapnik, to solve it, we need to install “boost-python3” firstly.

%brew install boost-python3

After installed “boost-python3”, execute the command

%ls -l /usr/local/lib/libboost_python*

to check the installed version, you can see libboost_python39 has been installed on my computer.

Now, start to install the “python-mapnik” library:

// "39" should be the install version as same as above
%export BOOST_PYTHON_LIB=boost_python39
%python3 setup.py install

After the installation is finished, you should see the below result.

You can execute the following commands to test if it is installed successfully:

%git submodule update —init
%python3 setup.py test

Now, we have installed “mapnik” and “python-mapnik” successfully in the system, you can execute a simple python code to get where the library is stored as below, please notice that you might need to restart your terminal instance for Python to identify the library.

Welcome to comment if you have any questions.

Categories
Web Map

Build your self-host world map – Download and import map data into PostGIS

This article logs the process of building my on-premise web map and talks about I use OpenStreetMap to download world map data and import it into PostGIS for late use.

What is OpenStreetMap(OSM)

OpenStreetMap(OSM) is a collaborative project to create a free editable map of the world. The geodata underlying the map is considered the primary output of the project. The creation and growth of OSM have been motivated by restrictions on the use or availability of map data across much of the world, and the advent of inexpensive portable satellite navigation devices.

Where to download map data

You can download OSM data from the official website https://planet.openstreetmap.org , or from a mirror website https://wiki.openstreetmap.org/wiki/Planet.osm#Downloading , and I suggest you download a small part such as a city for testing purpose. I downloaded the New Zealand data as below for testing:

From: http://download.geofabrik.de/

Pre-requisites

We need to install the importing tool “osm2pgsql” and “PostGIS” on the computer. For installing “osm2pgsql”, please refer: https://osm2pgsql.org/doc/install.html, for PostGIS, you can refer to my previous post “Install PostgreSQL 13 with PostGIS and PgAdmin 4 with Docker on Mac” if your laptop is MacBook.

Let’s start the process

  1. Create new user “osmuser” without any database creation privileges in PostgreSQL
    CREATE ROLE osmuser WITH LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE INHERIT NOREPLICATION CONNECTION LIMIT -1 PASSWORD ‘123456’; COMMENT ON ROLE osmuser IS ‘User for OpenStreetMap(OSM)’;
  2. Create the new database “osm” in PostgreSQL
    CREATE DATABASE osm WITH OWNER = osmuser ENCODING = ‘UTF8’ CONNECTION LIMIT = -1;
  3. Add the extensions “postgis” and “hstore” in the database
    CREATE EXTENSION postgis; CREATE EXTENSION hstore;
  4. Import it into the database:
    osm2pgsql new-zealand-latest.osm.pbf -d osm -H localhost -U postgres -W

After executed successfully, you can see 4 tables have been created and OSM data have been inserted into the database.

Categories
Web Map

Install PostgreSQL 13 with PostGIS and PgAdmin 4 with Docker on Mac

PostGIS is the best Geospatial DB solution for applications besides the geographic world. This article is a guide for building the environment of PostgreSQL 13 with PostGIS and PgAdmin 4 using Docker on Mac. I assume you have already installed Docker on your Mac so just introduce how to install PostGIS and PgAdmin 4.

  1. Create a new Docker network
$docker network create --driver bridge postgis-network

2. Create the new PostGIS Docker container with the above network

docker run --name postgis -p 5432:5432 -e POSTGRES_PASSWORD=123456 --hostname postgis --network postgis-network --detach postgis/postgis

3. Create the new PgAdmin4 Docker container with the above network

docker run --name pgadmin4 -p 5050:80 -e "PGADMIN_DEFAULT_EMAIL=ukalpa@gmail.com" -e "PGADMIN_DEFAULT_PASSWORD=123456" --hostname pgadmin4 --network postgis-network --detach dpage/pgadmin4

4. Open the installed PgAdmin in the browser, then login and connect the PostGIS server.

Execute the query “SELECT * FROM pg_available_extensions;”, we can then available PostGIS extensions in the list.

Open PostGIS extension in the specific database

CREATE EXTENSION postgis;
CREATE EXTENSION postgis_raster;
-- Enable Topology
CREATE EXTENSION postgis_topology;
-- Enable PostGIS Advanced 3D
-- and other geoprocessing algorithms
-- sfcgal not available with all distributions
CREATE EXTENSION postgis_sfcgal;
-- fuzzy matching needed for Tiger
CREATE EXTENSION fuzzystrmatch;
-- rule based standardizer
CREATE EXTENSION address_standardizer;
-- example rule data set
CREATE EXTENSION address_standardizer_data_us;
-- Enable US Tiger Geocoder
CREATE EXTENSION postgis_tiger_geocoder;

Now, you have successfully opened PostGIS in the database, you can execute the following query to check

SELECT postgis_full_version();