Plot geospatial data (xfig example)

From Remeis-Wiki
Jump to navigation Jump to search

Read and use geospatial data

Natural earth provides a huge and free collection of vector and raster data for drawing maps of the earth. Most of the files are distributed as ESRI shape files. Here is demonstrated how you can use the slang wrapper around the shapelib allowing to read this files.

Plot world jjs.png

Plot world vector jjs.png

Be aware that the resulting pdf files are huge and are scaled to a physical size of roughly 2 by 1 meter! The images here are only a down sampled version.

The shapelib slang wrapper can be obtained from our git page (internal only). There are numerous bugs, and be prepared to face core dumps when you use it!

require("xfig");
require("shapelib");


% the relevant files for the lines we want to draw
% source is  -- https://www.naturalearthdata.com/ --
% The raster data is taken from the geotiff files and
% converted to simple grayscale png files. All geo data
% is droped and there is no way to get the projection information
% form the WKT (.prj) files! (for me, I have no idea if and how
% this is standardized...)

variable SOURCE = struct {
  coastline = "ne_10m_coastline",
  land = "ne_10m_land",
  islands_coastline = "ne_10m_minor_islands_coastline",
  islands = "ne_10m_minor_islands",
  reefs = "ne_10m_reefs",
  ocean = "ne_10m_ocean",
  rivers = "ne_10m_rivers_lake_centerlines",
  lakes = "ne_10m_lakes",
  grid = struct {
    deg1 = "ne_10m_graticules_1",
    deg5 = "ne_10m_graticules_5",
    deg10 = "ne_10m_graticules_10",
    deg15 = "ne_10m_graticules_15",
    deg20 = "ne_10m_graticules_20",
    deg30 = "ne_10m_graticules_30"
  },
  timezones = "ne_10m_time_zones"
};

variable current_shape;

current_shape = shape_open(SOURCE.coastline, "r");

% lets plot the map in a plot window
variable PLOT = xfig_plot_new(40, 20); % it's to small, but we scale later
PLOT.world(-180,180,-90,90); % that is the coordinates
PLOT.title("Map of the Earth"; size="\\Huge");
PLOT.xlabel("Longitude"; size="\\large");
PLOT.ylabel("Latitude"; size="\\large");
PLOT.xaxis(;major=[-180:180:15], minor=[-180:180:5][where([-180:180:5] mod 15)],
	   format="$%d^\\circ$", major_grid=1, major_line=0, major_width=1, color="#a5a5a5",
	   minor_grid=1, minor_line=1, minor_width=1,
	   ticlabel_size="\\small");
PLOT.yaxis(;major=[-90:90:15], minor=[-90:90:5][where([-90:90:5] mod 15)],
	   format="$%d^\\circ$", major_grid=1, major_line=0, major_width=1, color="#a5a5a5",
	   minor_grid=1, minor_line=1, minor_width=1,
	   ticlabel_size="\\small");

% Define a second plot window so we can have nicer tic marks
variable MARKS = xfig_plot_new(40, 20);
MARKS.world(-180,180,-90,90);
MARKS.xaxis(; major=[-180:180:15], minor=[-180:180:5][where([-180:180:5] mod 15)],
	    ticlabels=0, depth=1, width=3);
MARKS.yaxis(; major=[-90:90:15], minor=[-90:90:5][where([-90:90:5] mod 15)],
	    ticlabels=0, depth=1, width=3);

% the shape struct provides a .next() function to iterate over
% the contents. If we are at the last one it returns null
variable s = current_shape.next();
do {
  PLOT.plot(s.X, s.Y);
  s = current_shape.next();
} while (s != NULL);


% This is commented out, because we use a neat picture to visualize

% Iterate over the land polygons
%current_shape.close(); % close the stuff, we don't want to occupy all that memory
%current_shape = shape_open(SOURCE.land, "r");
%s = current_shape.next();
% The land polygons are a different data type of the shape file, guess what, polygons.
% These are stored as 'parts' in one returned struct. So we have to use the info
% where a part starts (and stops) to draw the correct polygons
variable i, nparts, w;
%do {
%  nparts = length(s.PartStart);
%  _for i (0, nparts-2, 1) {
%    w = [s.PartStart[i]:s.PartStart[i+1]-1];
%    PLOT.shade_region(s.X[w], s.Y[w]; fillcolor="#552525");
%  }
%  w = [s.PartStart[nparts-1]:nparts-1];
%  PLOT.shade_region(s.X[w], s.Y[w]; fillcolor="#552525");
%  s = current_shape.next();
%} while (s != NULL);

% Next on the list is the coastline of the islands
current_shape.close();
current_shape = shape_open(SOURCE.islands_coastline, "r");
s = current_shape.next();
do {
  PLOT.plot(s.X, s.Y); % again ordinary lines, no special treatment necessary
  s = current_shape.next();
} while (s != NULL);

% And also the corresponding land masses
current_shape.close();
current_shape = shape_open(SOURCE.islands, "r");
s = current_shape.next();
do {
  nparts = length(s.PartStart);
  _for i (0, nparts-2, 1) {
    w = [s.PartStart[i]:s.PartsStart[i+1]];
    PLOT.shade_region(s.X[w], s.Y[w]; fillcolor="552525");
  }
  w = [s.PartStart[nparts-1]:nparts-1];
  PLOT.shade_region(s.X[w], s.Y[w]; fillcolor="552525");
  s = current_shape.next();
} while (s != NULL);

% Skip reefs, who needs them, right?
% Go straight to rivers
current_shape.close();
current_shape = shape_open(SOURCE.rivers, "r");
s = current_shape.next();
% These are arcs or polylines, but given as different parts. Sooooo
% same strategy as for the land masses
do {
  nparts = length(s.PartStart);
  if (s.ShapeType == 0) { s = current_shape.next(); continue; } % Type 0 are Null shapes, i.e., have only meta data. skip them
  _for i (0, nparts-2, 1) {
    w = [s.PartStart[i]:s.PartStart[i+1]-1];
    PLOT.plot(s.X[w], s.Y[w]; color="#2525E5");
  }
  w = [s.PartStart[nparts-1]:nparts-1];
  PLOT.plot(s.X[w], s.Y[w]; color="#2525E5");
  s = current_shape.next();
} while (s != NULL);

% Now deal with the lakes, polygons of course
% well, don't they are way to small
%current_shape.close();
%current_shape = shape_open(SOURCE.rivers, "r");
%s = current_shape.next();
%do {
%  nparts = length(s.PartStart);
%  if (s.ShapeType == 0) { s = current_shape.next(); continue; }
%  _for i (0, nparts-2, 1) {
%    w = [s.PartStart[i]:s.PartStart[i+1]-1];
%    PLOT.shade_region(s.X[w], s.Y[w]; fillcolor="#2525E5");
%  }
%  w = [s.PartStart[nparts-1]:nparts-1];
%  PLOT.shade_region(s.X[w], s.Y[w]; fillcolor="#2525E5");
%  s = current_shape.next();
%} while (s != NULL);

% Plot some grid lines
% Well no, we simply use the coordinate grid
%current_shape.close();
%current_shape = shape_open(SOURCE.grid.deg30, "r");
%s = current_shape.next();
%do {
%  PLOT.plot(s.X, s.Y);
%  s = current_shape.next();
%} while (s != NULL);

% Finally get some time zones on the map
current_shape.close();
current_shape = shape_open(SOURCE.timezones, "r");
s = current_shape.next();
do {
  PLOT.plot(s.X, s.Y; line=2, color="#858585");
  s = current_shape.next();
} while (s != NULL);


% For a nice map, use a image. This is also from natural earth, however I manipulated
% some of the resources to get the result.
% GS TAKES FOREVER TO RENDER THIS, comment it out if you want to test the shapelib
%PLOT.plot_png("world.png"; depth=1000);

variable C = xfig_new_compound(PLOT, MARKS);
C.scale(5);
C.render("Map-vector.pdf");