[Git][debian-gis-team/osm-gps-map][upstream] New upstream version 1.2.2
Bas Couwenberg (@sebastic)
gitlab at salsa.debian.org
Mon Oct 12 04:32:17 BST 2026
Bas Couwenberg pushed to branch upstream at Debian GIS Project / osm-gps-map
Commits:
04511e21 by Bas Couwenberg at 2026-10-12T05:21:28+02:00
New upstream version 1.2.2
- - - - -
28 changed files:
- .github/workflows/ci.yml
- .github/workflows/pages.yml
- .gitignore
- Makefile.am
- NEWS
- configure.ac
- examples/Makefile.am
- examples/README
- examples/editable_track.c
- examples/mapviewer.c
- examples/mapviewer.js
- examples/mapviewer.py
- examples/polygon.c
- catalog.xml → osmgpsmap.xml
- + scripts/releases-data.py
- src/converter.c
- src/osm-gps-map-layer.c
- src/osm-gps-map-layer.h
- src/osm-gps-map-track.c
- src/osm-gps-map-widget.c
- tests/test.py
- + website/Gemfile
- + website/Gemfile.lock
- + website/_config.yml
- + website/_data/releases.json
- + website/index.md
- + website/screenshot-osd.png
- + website/screenshot.png
Changes:
=====================================
.github/workflows/ci.yml
=====================================
@@ -10,7 +10,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout at v5
+ - uses: actions/checkout at v7
- name: Install dependencies
run: |
@@ -28,6 +28,7 @@ jobs:
libgirepository1.0-dev \
python3-gi \
python3-cairo \
+ python3-gi-cairo \
gir1.2-gtk-3.0 \
gir1.2-gdkpixbuf-2.0 \
xvfb
=====================================
.github/workflows/pages.yml
=====================================
@@ -1,28 +1,32 @@
name: Pages
-# Deploy gtk-doc HTML to the gh-pages branch on every published release
-# (RC or final). Landing page + screenshots on gh-pages are preserved.
+# Tag is enough to publish (gtk-doc + Jekyll). A GitHub Release is
+# required only for the landing-page Download / News list.
on:
- release:
- types: [published]
+ push:
+ branches:
+ - master
+ tags:
+ - "*"
+ workflow_dispatch:
permissions:
- contents: write
+ contents: read
+ pages: write
+ id-token: write
+
+concurrency:
+ group: pages
+ cancel-in-progress: false
jobs:
- docs:
+ build:
runs-on: ubuntu-latest
steps:
- - name: Checkout release
- uses: actions/checkout at v5
-
- - name: Checkout gh-pages
- uses: actions/checkout at v5
- with:
- ref: gh-pages
- path: _gh-pages
+ - name: Checkout
+ uses: actions/checkout at v7
- - name: Install dependencies
+ - name: Install gtk-doc dependencies
run: |
sudo apt-get update
sudo apt-get install -y \
@@ -41,21 +45,51 @@ jobs:
run: |
./autogen.sh --prefix=/usr --enable-gtk-doc
make -j"$(nproc)"
+ test -d docs/reference/html
- - name: Update API docs on gh-pages
+ - name: Write releases data
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
- set -e
- test -d docs/reference/html
- rm -rf _gh-pages/docs/reference/html
- mkdir -p _gh-pages/docs/reference
- cp -a docs/reference/html _gh-pages/docs/reference/
- cd _gh-pages
- git config user.name "github-actions[bot]"
- git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- git add -A docs/reference/html
- if git diff --staged --quiet; then
- echo "No docs changes"
- exit 0
- fi
- git commit -m "docs: API reference for ${{ github.event.release.tag_name }}"
- git push origin HEAD:gh-pages
+ python3 scripts/releases-data.py \
+ --repo "${{ github.repository }}" \
+ --out website/_data/releases.json
+
+ - name: Setup Ruby
+ uses: ruby/setup-ruby at v1
+ with:
+ ruby-version: "3.3"
+ bundler-cache: true
+ working-directory: website
+
+ - name: Setup Pages
+ id: pages
+ uses: actions/configure-pages at v6
+
+ - name: Build with Jekyll
+ working-directory: website
+ env:
+ JEKYLL_ENV: production
+ run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
+
+ - name: Copy gtk-doc into site
+ run: |
+ mkdir -p website/_site/docs/reference
+ rm -rf website/_site/docs/reference/html
+ cp -a docs/reference/html website/_site/docs/reference/
+
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact at v5
+ with:
+ path: website/_site
+
+ deploy:
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ runs-on: ubuntu-latest
+ needs: build
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages at v5
=====================================
.gitignore
=====================================
@@ -58,3 +58,8 @@ examples/editable_track
examples/polygon
m4/gnome-compiler-flags.m4
m4/pkg.m4
+website/_site/
+website/.jekyll-cache/
+website/.jekyll-metadata
+website/vendor/
+website/.bundle/
=====================================
Makefile.am
=====================================
@@ -1,8 +1,11 @@
## Process this file with automake to produce Makefile.in
SUBDIRS = src examples docs
+# website/ is GitHub Pages (Jekyll). Not in SUBDIRS or EXTRA_DIST so
+# release tarballs stay the library (src, examples, gtk-doc).
AM_DISTCHECK_CONFIGURE_FLAGS = \
- --enable-gtk-doc
+ --enable-gtk-doc \
+ --enable-glade-catalog
ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS}
@@ -16,8 +19,12 @@ doc_DATA = \
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = osmgpsmap-1.0.pc
-# Unit tests ship in the tarball (CI/git also run them in-tree).
-EXTRA_DIST = tests/test.py
+if ENABLE_GLADE_CATALOG
+gladecatalog_DATA = osmgpsmap.xml
+endif
+
+# tests/test.py and osmgpsmap.xml ship in the tarball even if not installed.
+EXTRA_DIST = tests/test.py osmgpsmap.xml
DISTCLEANFILES = gtk-doc.make
=====================================
NEWS
=====================================
@@ -1,4 +1,20 @@
+Changes in master
+=================
+
+Changes in 1.2.2
+================
+ * Don't use floats for division because of rounding errors; fixes center hopping (znuh)
+ * Avoid div-by-zero in latlon2zoom when a latitude or longitude distance is zero (Hubert Kowalski)
+ * Floor tile origin for negative map-x so overlays stay aligned (Hubert Kowalski)
+ * Fix insert_point double-free (Hubert Kowalski)
+ * Hold the map until tile downloads complete so leftover Soup callbacks cannot touch freed tile state (Hubert Kowalski)
+ * Fix map redraw using width as height (Hubert Kowalski)
+ * Show drawing on the map in the examples (Hubert Kowalski)
+ * Add optional Glade catalog installation (Hubert Kowalski)
+ * Correctly support LFS (David Paleino, Bas Couwenberg)
+
Changes in 1.2.1
+======================
* Replace deprecated G_TYPE_INSTANCE_GET_PRIVATE and g_type_class_add_private (Patrick Salecker)
* Add python unit tests (Patrick Salecker)
* Fix python example addressing requiring map source (Jochen Sprickerhof)
=====================================
configure.ac
=====================================
@@ -1,6 +1,6 @@
AC_PREREQ([2.63])
AC_INIT([osm-gps-map],
- [1.2.1],
+ [1.2.2],
[http://github.com/nzjrs/osm-gps-map/issues],
[osm-gps-map])
@@ -21,6 +21,8 @@ AM_PROG_LIBTOOL
AM_SILENT_RULES([yes])
+AC_SYS_LARGEFILE
+
# Library dependencies
PKG_CHECK_MODULES(GLIB, [glib-2.0 >= 2.16])
PKG_CHECK_MODULES(GTK, [gtk+-3.0 >= 3.0])
@@ -31,6 +33,35 @@ PKG_CHECK_MODULES(SOUP30, [libsoup-3.0])
# libgthread-2.0.
PKG_CHECK_MODULES(GTHREAD, [gthread-2.0])
+AC_ARG_ENABLE([glade-catalog],
+ [AS_HELP_STRING([--enable-glade-catalog],
+ [install the Glade catalog (default=no)])],
+ [enable_glade_catalog=$enableval],
+ [enable_glade_catalog=no])
+case "$enable_glade_catalog" in
+ yes|no) ;;
+ *) AC_MSG_ERROR([--enable-glade-catalog accepts only yes or no]) ;;
+esac
+AM_CONDITIONAL([ENABLE_GLADE_CATALOG],
+ [test "x$enable_glade_catalog" = xyes])
+
+AC_ARG_WITH([glade-catalogdir],
+ [AS_HELP_STRING([--with-glade-catalogdir=PATH],
+ [Glade catalog directory (default=DATADIR/glade/catalogs)])],
+ [gladecatalogdir=$withval],
+ [gladecatalogdir='${datadir}/glade/catalogs'])
+if test "${with_glade_catalogdir+set}" = set; then
+ case "$with_glade_catalogdir" in
+ yes|no|'')
+ AC_MSG_ERROR([--with-glade-catalogdir requires a directory path])
+ ;;
+ esac
+ if test "x$enable_glade_catalog" != xyes; then
+ AC_MSG_ERROR([--with-glade-catalogdir requires --enable-glade-catalog])
+ fi
+fi
+AC_SUBST([gladecatalogdir])
+
AC_CHECK_FUNCS(gdk_event_get_scroll_deltas)
AC_MSG_CHECKING([for Win32])
@@ -52,7 +83,7 @@ AM_CONDITIONAL(OS_WIN32, [test $os_win32 = yes])
# - If binary compatibility has been broken (eg removed or changed interfaces)
# change to C+1:0:0
# - If the interface is the same as the previous version, change to C:R+1:A
-LT_VERSION_INFO=2:1:1
+LT_VERSION_INFO=2:2:1
AC_SUBST(LT_VERSION_INFO)
GOBJECT_INTROSPECTION_REQS=0.10.0
@@ -76,4 +107,8 @@ echo
echo Prefix............... : $prefix
echo Introspection support : ${found_introspection}
echo gtk-doc documentation : ${enable_gtk_doc}
+echo Glade catalog........ : ${enable_glade_catalog}
+if test "x$enable_glade_catalog" = xyes; then
+ echo Glade catalog dir.... : ${gladecatalogdir}
+fi
echo
=====================================
examples/Makefile.am
=====================================
@@ -63,5 +63,5 @@ editable_track_LDADD = \
$(top_builddir)/src/libosmgpsmap-1.0.la
## Misc
-EXTRA_DIST = poi.png mapviewer.ui mapviewer.js README
+EXTRA_DIST = poi.png mapviewer.ui mapviewer.py mapviewer.js README
=====================================
examples/README
=====================================
@@ -1,17 +1,39 @@
osm-gps-map example applications
+
+Run C and Python demos from this directory (examples) so they find mapviewer.ui
+and poi.png.
+
+If running uninstalled, you probably need to tell GObject introspection where
+to find the bindings. Do the following from the example directory:
+
+ export LD_LIBRARY_PATH=../src/.libs/
+ export GI_TYPELIB_PATH=../src/
+
* ./mapviewer
- C based demo app with many options. See '--help' for a list. Note, this
- must be executed from the examples directory so it can find the mapviewer.ui
- file.
+ C based demo app with many options. See '--help' for a list of options.
+ --editable-tracks makes the right-click track draggable.
* ./polygon
- This example demonstrates editable polygons. Vertex points can be dragged.
+ This example demonstrates editable polygons. Vertices can be dragged.
Clicking mid-points divides the line and creates another vertex.
+ * ./editable_track
+ One editable track. Same degree constructor as polygon.
* ./mapviewer.py
Python version of the C demo app, with examples showing how to do custom
- layers.
+ layers: DrawLayer class for cairo overlay, GPS, programmatic image_add,
+ and track_add.
* ./mapviewer.js
- Javascript example using seed + gobject-introspection. If running uninstalled,
- you probbably need to tell GObject introspection where to find the
- bindings. Do the following from the example directory
- # export LD_LIBRARY_PATH=../src/.libs/
- # export GI_TYPELIB_PATH=../src/
+ Javascript example using seed + gobject-introspection.
+
+Examples for drawing on the map:
+
+ * Double-left-click places a GPS point (blue ball+random heading).
+ * Double-right-click (or Ctrl+double-left) adds points to tracks.
+ Triple right click clears the track (a new empty track stays on the map).
+ This shows the use of track_add, add_point and track_remove
+ * Double-middle-click (or Shift+double-left) adds a marker via image_add.
+ Python: poi.png by default, or uncheck "Use poi.png for markers" to
+ paint a cairo star (programmatic pixbuf). C: poi.png, or a cairo star
+ if that file is missing. Triple middle click removes the last image.
+ * DrawLayer in mapviewer and mapviewer.py uses live cairo to draw a red house
+ at Home location (Timaru). Peak of the roof is at the lat/lon of
+ Home location.
=====================================
examples/editable_track.c
=====================================
@@ -21,8 +21,8 @@ main (int argc,
OsmGpsMapTrack* track = osm_gps_map_track_new();
OsmGpsMapPoint* p1, *p2;
- p1 = osm_gps_map_point_new_radians(1.25663706, -0.488692191);
- p2 = osm_gps_map_point_new_radians(1.06465084, -0.750491578);
+ p1 = osm_gps_map_point_new_degrees(72.0, -28.0);
+ p2 = osm_gps_map_point_new_degrees(61.0, -43.0);
osm_gps_map_track_add_point(track, p1);
osm_gps_map_track_add_point(track, p2);
=====================================
examples/mapviewer.c
=====================================
@@ -18,10 +18,10 @@
*/
#include <stdlib.h>
-#include <math.h>
#include <glib.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
+#include <cairo.h>
#include "osm-gps-map.h"
@@ -50,18 +50,141 @@ static GOptionEntry debug_entries[] =
};
#endif
+/* Shared with mapviewer.py DrawLayer Home (Timaru). */
+#define HOME_LAT -44.39
+#define HOME_LON 171.25
+#define HOME_ZOOM 12
+
+typedef struct _DrawLayer {
+ GObject parent;
+} DrawLayer;
+
+typedef struct _DrawLayerClass {
+ GObjectClass parent_class;
+} DrawLayerClass;
+
+static void draw_layer_interface_init (OsmGpsMapLayerIface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (DrawLayer, draw_layer, G_TYPE_OBJECT,
+ G_IMPLEMENT_INTERFACE (OSM_TYPE_GPS_MAP_LAYER,
+ draw_layer_interface_init));
+
+static void
+draw_layer_draw (OsmGpsMapLayer *layer G_GNUC_UNUSED,
+ OsmGpsMap *map, cairo_t *cr)
+{
+ OsmGpsMapPoint *pt;
+ gint x, y;
+
+ pt = osm_gps_map_point_new_degrees (HOME_LAT, HOME_LON);
+ osm_gps_map_convert_geographic_to_screen (map, pt, &x, &y);
+ osm_gps_map_point_free (pt);
+
+ cairo_set_source_rgba (cr, 1.0, 0.0, 0.0, 0.6);
+ cairo_move_to (cr, x, y);
+ cairo_line_to (cr, x + 12, y + 10);
+ cairo_line_to (cr, x + 12, y + 24);
+ cairo_line_to (cr, x - 12, y + 24);
+ cairo_line_to (cr, x - 12, y + 10);
+ cairo_close_path (cr);
+ cairo_fill (cr);
+}
+
+static void
+draw_layer_render (OsmGpsMapLayer *layer G_GNUC_UNUSED,
+ OsmGpsMap *map G_GNUC_UNUSED)
+{
+}
+
+static gboolean
+draw_layer_busy (OsmGpsMapLayer *layer G_GNUC_UNUSED)
+{
+ return FALSE;
+}
+
+static gboolean
+draw_layer_button_press (OsmGpsMapLayer *layer G_GNUC_UNUSED,
+ OsmGpsMap *map G_GNUC_UNUSED,
+ GdkEventButton *event G_GNUC_UNUSED)
+{
+ return FALSE;
+}
+
+static void
+draw_layer_interface_init (OsmGpsMapLayerIface *iface)
+{
+ iface->render = draw_layer_render;
+ iface->draw = draw_layer_draw;
+ iface->busy = draw_layer_busy;
+ iface->button_press = draw_layer_button_press;
+}
+
+static void
+draw_layer_class_init (DrawLayerClass *klass G_GNUC_UNUSED)
+{
+}
+
+static void
+draw_layer_init (DrawLayer *self G_GNUC_UNUSED)
+{
+}
+
static GdkPixbuf *g_star_image = NULL;
static OsmGpsMapImage *g_last_image = NULL;
+static OsmGpsMapTrack *g_click_track = NULL;
+
+/* Programmatic marker if poi.png is missing. Live cairo overlay is DrawLayer. */
+static GdkPixbuf *
+star_pixbuf_new (int size)
+{
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ GdkPixbuf *pixbuf;
+ int i;
+ double outer = size / 2.0 - 1.0;
+ double inner = size / 5.0;
+
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, size, size);
+ cr = cairo_create (surface);
+
+ cairo_translate (cr, size / 2.0, size / 2.0);
+ cairo_move_to (cr, 0, -outer);
+ for (i = 1; i < 10; i++) {
+ cairo_rotate (cr, G_PI / 5.0);
+ cairo_line_to (cr, 0, (i % 2 == 0) ? -outer : -inner);
+ }
+ cairo_close_path (cr);
+ cairo_set_source_rgb (cr, 1.0, 0.85, 0.0);
+ cairo_fill_preserve (cr);
+ cairo_set_source_rgb (cr, 0.75, 0.45, 0.0);
+ cairo_set_line_width (cr, 1);
+ cairo_stroke (cr);
+
+ pixbuf = gdk_pixbuf_get_from_surface (surface, 0, 0, size, size);
+ cairo_destroy (cr);
+ cairo_surface_destroy (surface);
+ return pixbuf;
+}
+
+static OsmGpsMapTrack *
+click_track_new (void)
+{
+ OsmGpsMapTrack *track = osm_gps_map_track_new ();
+ if (opt_editable_tracks)
+ g_object_set (track, "editable", TRUE, NULL);
+ return track;
+}
static gboolean
-on_button_press_event (GtkWidget *widget, GdkEventButton *event, gpointer user_data)
+on_button_press_event (GtkWidget *widget, GdkEventButton *event, gpointer user_data G_GNUC_UNUSED)
{
OsmGpsMapPoint coord;
float lat, lon;
OsmGpsMap *map = OSM_GPS_MAP(widget);
- OsmGpsMapTrack *othertrack = OSM_GPS_MAP_TRACK(user_data);
+ /* 2BUTTON_PRESS often has BUTTONn_MASK set; ignore those, keep Shift/Ctrl. */
+ GdkModifierType mods = event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK);
- int left_button = (event->button == 1) && (event->state == 0);
+ int left_button = (event->button == 1) && (mods == 0);
int middle_button = (event->button == 2) || ((event->button == 1) && (event->state & GDK_SHIFT_MASK));
int right_button = (event->button == 3) || ((event->button == 1) && (event->state & GDK_CONTROL_MASK));
@@ -70,11 +193,16 @@ on_button_press_event (GtkWidget *widget, GdkEventButton *event, gpointer user_d
if (event->type == GDK_3BUTTON_PRESS) {
if (middle_button) {
- if (g_last_image)
+ if (g_last_image) {
osm_gps_map_image_remove (map, g_last_image);
+ g_last_image = NULL;
+ }
}
if (right_button) {
- osm_gps_map_track_remove(map, othertrack);
+ osm_gps_map_track_remove (map, g_click_track);
+ g_object_unref (g_click_track);
+ g_click_track = click_track_new ();
+ osm_gps_map_track_add (map, g_click_track);
}
} else if (event->type == GDK_2BUTTON_PRESS) {
if (left_button) {
@@ -83,14 +211,14 @@ on_button_press_event (GtkWidget *widget, GdkEventButton *event, gpointer user_d
lon,
g_random_double_range(0,360));
}
- if (middle_button) {
+ if (middle_button && g_star_image) {
g_last_image = osm_gps_map_image_add (map,
lat,
lon,
g_star_image);
}
if (right_button) {
- osm_gps_map_track_add_point(othertrack, &coord);
+ osm_gps_map_track_add_point(g_click_track, &coord);
}
}
@@ -105,7 +233,7 @@ on_map_changed_event (GtkWidget *widget, gpointer user_data)
OsmGpsMap *map = OSM_GPS_MAP(widget);
g_object_get(map, "latitude", &lat, "longitude", &lon, NULL);
- gchar *msg = g_strdup_printf("Map Centre: lattitude %f longitude %f",lat,lon);
+ gchar *msg = g_strdup_printf("Map Centre: latitude %f longitude %f",lat,lon);
gtk_entry_set_text(entry, msg);
g_free(msg);
@@ -136,7 +264,7 @@ static gboolean
on_home_clicked_event (GtkWidget *widget, gpointer user_data)
{
OsmGpsMap *map = OSM_GPS_MAP(user_data);
- osm_gps_map_set_center_and_zoom(map, -43.5326,172.6362,12);
+ osm_gps_map_set_center_and_zoom(map, HOME_LAT, HOME_LON, HOME_ZOOM);
return FALSE;
}
@@ -246,8 +374,7 @@ main (int argc, char **argv)
GtkWidget *widget;
GtkAccelGroup *ag;
OsmGpsMap *map;
- OsmGpsMapLayer *osd;
- OsmGpsMapTrack *rightclicktrack;
+ OsmGpsMapLayer *osd, *draw;
const char *repo_uri;
char *cachedir, *cachebasedir;
GError *error = NULL;
@@ -318,12 +445,16 @@ main (int argc, char **argv)
osm_gps_map_layer_add(OSM_GPS_MAP(map), osd);
g_object_unref(G_OBJECT(osd));
- //Add a second track for right clicks
- rightclicktrack = osm_gps_map_track_new();
+ draw = g_object_new (draw_layer_get_type (), NULL);
+ osm_gps_map_layer_add (OSM_GPS_MAP (map), draw);
+ g_object_unref (draw);
+
+ /* Stay put on gps_add so the blue blob is not under the OSD crosshair. */
+ g_object_set (map, "auto-center", FALSE, NULL);
- if(opt_editable_tracks)
- g_object_set(rightclicktrack, "editable", TRUE, NULL);
- osm_gps_map_track_add(OSM_GPS_MAP(map), rightclicktrack);
+ g_click_track = click_track_new ();
+ osm_gps_map_track_add (OSM_GPS_MAP (map), g_click_track);
+ osm_gps_map_set_center_and_zoom (map, HOME_LAT, HOME_LON, HOME_ZOOM);
g_free(cachedir);
g_free(cachebasedir);
@@ -336,7 +467,15 @@ main (int argc, char **argv)
osm_gps_map_set_keyboard_shortcut(map, OSM_GPS_MAP_KEY_RIGHT, GDK_KEY_Right);
//Build the UI
- g_star_image = gdk_pixbuf_new_from_file_at_size ("poi.png", 24,24,NULL);
+ {
+ GError *pixbuf_err = NULL;
+ g_star_image = gdk_pixbuf_new_from_file_at_size ("poi.png", 24, 24, &pixbuf_err);
+ if (!g_star_image) {
+ g_printerr ("poi.png: %s\n", pixbuf_err ? pixbuf_err->message : "missing");
+ g_clear_error (&pixbuf_err);
+ g_star_image = star_pixbuf_new (24);
+ }
+ }
builder = gtk_builder_new();
gtk_builder_add_from_file (builder, "mapviewer.ui", &error);
@@ -404,7 +543,7 @@ main (int argc, char **argv)
gtk_builder_get_object(builder, "star_yalign_adjustment"), "value-changed",
G_CALLBACK (on_star_align_changed), (gpointer) "y-align");
g_signal_connect (G_OBJECT (map), "button-press-event",
- G_CALLBACK (on_button_press_event), (gpointer) rightclicktrack);
+ G_CALLBACK (on_button_press_event), NULL);
g_signal_connect (G_OBJECT (map), "changed",
G_CALLBACK (on_map_changed_event),
(gpointer) gtk_builder_get_object(builder, "text_entry"));
=====================================
examples/mapviewer.js
=====================================
@@ -1,6 +1,6 @@
#!/usr/bin/seed
-// You probbably need to tell GObject introspection where to find the bindings
+// You probably need to tell GObject introspection where to find the bindings
// export LD_LIBRARY_PATH=../src/.libs/
// export GI_TYPELIB_PATH=../src/
=====================================
examples/mapviewer.py
=====================================
@@ -17,7 +17,10 @@ You should have received a copy of the GNU General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
"""
+import math
import random
+
+import cairo
import gi
gi.require_version("Gdk", "3.0")
@@ -33,36 +36,58 @@ from gi.repository import (
OsmGpsMap as osmgpsmap,
) # noqa
-print(f"using library: {osmgpsmap.__file__} (version {osmgpsmap._version})")
+print(f"using {osmgpsmap} (version {osmgpsmap._version})")
assert osmgpsmap._version == "1.0"
+# Timaru. DrawLayer paints a house here; click Home or start here to see it.
+HOME_LAT = -44.39
+HOME_LON = 171.25
+HOME_ZOOM = 12
+
+
class DummyMapNoGpsPoint(osmgpsmap.Map):
- def do_draw_gps_point(self, drawable):
+ def do_draw_gps_point(self, cr):
pass
GObject.type_register(DummyMapNoGpsPoint)
-class DummyLayer(GObject.GObject, osmgpsmap.MapLayer):
+class DrawLayer(GObject.GObject, osmgpsmap.MapLayer):
+ """Cairo overlay. The map passes its cairo_t; do not create a surface.
+
+ For paths use track_add, for markers image_add. This class is the
+ live-cairo case: draw in widget pixels after convert_geographic_to_screen.
+ """
+
def __init__(self):
GObject.GObject.__init__(self)
- def do_draw(self, gpsmap, gdkdrawable):
- pass
+ def do_draw(self, gpsmap, cr):
+ pt = osmgpsmap.MapPoint.new_degrees(HOME_LAT, HOME_LON)
+ x, y = gpsmap.convert_geographic_to_screen(pt)
+ cr.set_source_rgba(1.0, 0.0, 0.0, 0.6)
+ cr.move_to(x, y)
+ cr.line_to(x + 12, y + 10)
+ cr.line_to(x + 12, y + 24)
+ cr.line_to(x - 12, y + 24)
+ cr.line_to(x - 12, y + 10)
+ cr.close_path()
+ cr.fill()
def do_render(self, gpsmap):
+ # OSD caches bitmaps here. Geo overlays paint in do_draw instead.
pass
def do_busy(self):
return False
- def do_button_press(self, gpsmap, gdkeventbutton):
+ def do_button_press(self, gpsmap, event):
return False
-GObject.type_register(DummyLayer)
+GObject.type_register(DrawLayer)
class UI(Gtk.Window):
@@ -79,40 +104,10 @@ class UI(Gtk.Window):
if 0:
self.osm = DummyMapNoGpsPoint()
else:
- self.osm = osmgpsmap.Map()
- self.osm.layer_add(
- osmgpsmap.MapOsd(show_dpad=True,
- show_zoom=True,
- show_crosshair=True)
- )
+ self.osm = osmgpsmap.Map(user_agent="mapviewer.py")
self.osm.set_property("map-source", osmgpsmap.MapSource_t.OPENSTREETMAP)
- self.osm.layer_add(DummyLayer())
-
- self.last_image = None
-
- self.osm.connect("button_press_event", self.on_button_press)
- self.osm.connect("changed", self.on_map_change)
-
- # connect keyboard shortcuts
- self.osm.set_keyboard_shortcut(
- osmgpsmap.MapKey_t.FULLSCREEN, Gdk.keyval_from_name("F11")
- )
- self.osm.set_keyboard_shortcut(
- osmgpsmap.MapKey_t.UP, Gdk.keyval_from_name("Up")
- )
- self.osm.set_keyboard_shortcut(
- osmgpsmap.MapKey_t.DOWN, Gdk.keyval_from_name("Down")
- )
- self.osm.set_keyboard_shortcut(
- osmgpsmap.MapKey_t.LEFT, Gdk.keyval_from_name("Left")
- )
- self.osm.set_keyboard_shortcut(
- osmgpsmap.MapKey_t.RIGHT, Gdk.keyval_from_name("Right")
- )
-
- # connect to tooltip
- self.osm.props.has_tooltip = True
- self.osm.connect("query-tooltip", self.on_query_tooltip)
+ self.use_poi_png = True
+ self._wire_map(self.osm)
self.latlon_entry = Gtk.Entry()
@@ -155,7 +150,7 @@ from in the box below. Special metacharacters may be included in this url
\t#S\tInverse zoom (max-zoom - #Z)
\t#Q\tQuadtree encoded tile (qrts)
\t#W\tQuadtree encoded tile (1234)
-\t#U\tEncoding not implemeted
+\t#U\tEncoding not implemented
\t#R\tRandom integer, 0-4"""
)
lbl.props.xalign = 0
@@ -185,6 +180,11 @@ from in the box below. Special metacharacters may be included in this url
cb.connect("toggled", self.on_show_tooltips_toggled)
self.vbox.pack_end(cb, False, True, 0)
+ cb = Gtk.CheckButton(label="Use poi.png for markers")
+ cb.props.active = self.use_poi_png
+ cb.connect("toggled", self.on_use_poi_png_toggled)
+ self.vbox.pack_end(cb, False, True, 0)
+
cb = Gtk.CheckButton(label="Disable Cache")
cb.props.active = False
cb.connect("toggled", self.disable_cache_toggled)
@@ -196,6 +196,44 @@ from in the box below. Special metacharacters may be included in this url
GLib.timeout_add(500, self.print_tiles)
+ def _wire_map(self, osm):
+ self.osm = osm
+ osm.layer_add(
+ osmgpsmap.MapOsd(show_dpad=True,
+ show_zoom=True,
+ show_crosshair=True)
+ )
+ osm.layer_add(DrawLayer())
+ osm.set_center_and_zoom(HOME_LAT, HOME_LON, HOME_ZOOM)
+ # Stay put on gps_add so the blue blob is not under the OSD crosshair.
+ osm.props.auto_center = False
+
+ self.click_track = osmgpsmap.MapTrack()
+ osm.track_add(self.click_track)
+ self.last_image = None
+
+ osm.connect("button_press_event", self.on_button_press)
+ osm.connect("changed", self.on_map_change)
+
+ osm.set_keyboard_shortcut(
+ osmgpsmap.MapKey_t.FULLSCREEN, Gdk.keyval_from_name("F11")
+ )
+ osm.set_keyboard_shortcut(
+ osmgpsmap.MapKey_t.UP, Gdk.keyval_from_name("Up")
+ )
+ osm.set_keyboard_shortcut(
+ osmgpsmap.MapKey_t.DOWN, Gdk.keyval_from_name("Down")
+ )
+ osm.set_keyboard_shortcut(
+ osmgpsmap.MapKey_t.LEFT, Gdk.keyval_from_name("Left")
+ )
+ osm.set_keyboard_shortcut(
+ osmgpsmap.MapKey_t.RIGHT, Gdk.keyval_from_name("Right")
+ )
+
+ osm.props.has_tooltip = True
+ osm.connect("query-tooltip", self.on_query_tooltip)
+
def disable_cache_toggled(self, btn):
if btn.props.active:
self.osm.props.tile_cache = osmgpsmap.MAP_CACHE_DISABLED
@@ -205,19 +243,23 @@ from in the box below. Special metacharacters may be included in this url
def on_show_tooltips_toggled(self, btn):
self.show_tooltips = btn.props.active
+ def on_use_poi_png_toggled(self, btn):
+ self.use_poi_png = btn.props.active
+
def load_map_clicked(self, button):
uri = self.repouri_entry.get_text()
format = self.image_format_entry.get_text()
if uri and format:
if self.osm:
- # remove old map
self.vbox.remove(self.osm)
try:
- self.osm = osmgpsmap.Map(repo_uri=uri, image_format=format)
+ osm = osmgpsmap.Map(
+ repo_uri=uri, image_format=format, user_agent="mapviewer.py"
+ )
except Exception as e:
print("ERROR:", e)
- self.osm = osmgpsmap.Map()
-
+ osm = osmgpsmap.Map(user_agent="mapviewer.py")
+ self._wire_map(osm)
self.vbox.pack_start(self.osm, True, True, 0)
self.osm.show()
@@ -233,15 +275,15 @@ from in the box below. Special metacharacters may be included in this url
self.osm.set_zoom(self.osm.props.zoom - 1)
def home_clicked(self, button):
- self.osm.set_center_and_zoom(-44.39, 171.25, 12)
+ self.osm.set_center_and_zoom(HOME_LAT, HOME_LON, HOME_ZOOM)
def on_query_tooltip(self, widget, x, y, keyboard_tip, tooltip, data=None):
if keyboard_tip:
return False
if self.show_tooltips:
- p = osmgpsmap.point_new_degrees(0.0, 0.0)
- self.osm.convert_screen_to_geographic(x, y, p)
+ # GI treats the MapPoint as an out-arg, so it is returned.
+ p = self.osm.convert_screen_to_geographic(x, y)
lat, lon = p.get_degrees()
tooltip.set_markup(f"{lat:+.4f}, {lon:+.4f}")
return True
@@ -263,10 +305,18 @@ from in the box below. Special metacharacters may be included in this url
)
def on_button_press(self, osm, event):
+ # Double-click: left GPS, middle image_add, right track_add.
+ # Triple-click: middle remove last image, right clear the track.
state = event.get_state()
lat, lon = self.osm.get_event_location(event).get_degrees()
- left = event.button == 1 and state == 0
+ # 2BUTTON_PRESS often has BUTTONn_MASK set; ignore those, keep Shift/Ctrl.
+ mods = state & (
+ Gdk.ModifierType.SHIFT_MASK
+ | Gdk.ModifierType.CONTROL_MASK
+ | Gdk.ModifierType.MOD1_MASK
+ )
+ left = event.button == 1 and mods == 0
middle = event.button == 2 or (
event.button == 1 and state & Gdk.ModifierType.SHIFT_MASK
)
@@ -283,14 +333,52 @@ from in the box below. Special metacharacters may be included in this url
if self.last_image is not None:
self.osm.image_remove(self.last_image)
self.last_image = None
+ if right:
+ self.osm.track_remove(self.click_track)
+ self.click_track = osmgpsmap.MapTrack()
+ self.osm.track_add(self.click_track)
elif event.type == GDK_2BUTTON_PRESS:
if left:
self.osm.gps_add(lat, lon, heading=random.random() * 360)
if middle:
- pb = GdkPixbuf.Pixbuf.new_from_file_at_size("poi.png", 24, 24)
- self.last_image = self.osm.image_add(lat, lon, pb)
+ self.last_image = self.osm.image_add(
+ lat, lon, self._marker_pixbuf()
+ )
if right:
- pass
+ pt = osmgpsmap.MapPoint.new_degrees(lat, lon)
+ self.click_track.add_point(pt)
+
+ def _marker_pixbuf(self):
+ if self.use_poi_png:
+ try:
+ return GdkPixbuf.Pixbuf.new_from_file_at_size("poi.png", 24, 24)
+ except GLib.Error as err:
+ print("poi.png:", err)
+ return self._star_pixbuf()
+
+ def _star_pixbuf(self, size=24):
+ # Programmatic image: cairo star -> pixbuf -> image_add.
+ # Live cairo on the map is DrawLayer, not this.
+ surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, size, size)
+ cr = cairo.Context(surface)
+ cx = cy = size / 2.0
+ outer, inner = size / 2.0 - 1.0, size / 5.0
+ for i in range(10):
+ r = outer if i % 2 == 0 else inner
+ ang = -math.pi / 2.0 + i * math.pi / 5.0
+ x = cx + r * math.cos(ang)
+ y = cy + r * math.sin(ang)
+ if i == 0:
+ cr.move_to(x, y)
+ else:
+ cr.line_to(x, y)
+ cr.close_path()
+ cr.set_source_rgb(1.0, 0.85, 0.0)
+ cr.fill_preserve()
+ cr.set_source_rgb(0.75, 0.45, 0.0)
+ cr.set_line_width(1)
+ cr.stroke()
+ return Gdk.pixbuf_get_from_surface(surface, 0, 0, size, size)
if __name__ == "__main__":
=====================================
examples/polygon.c
=====================================
@@ -23,9 +23,9 @@ main (int argc,
OsmGpsMapTrack* polytrack = osm_gps_map_track_new();
OsmGpsMapPoint* p1, *p2, *p3;
- p1 = osm_gps_map_point_new_radians(1.25663706, -0.488692191);
- p2 = osm_gps_map_point_new_radians(1.06465084, -0.750491578);
- p3 = osm_gps_map_point_new_radians(1.064650849, -0.191986218);
+ p1 = osm_gps_map_point_new_degrees(72.0, -28.0);
+ p2 = osm_gps_map_point_new_degrees(61.0, -43.0);
+ p3 = osm_gps_map_point_new_degrees(61.0, -11.0);
osm_gps_map_track_add_point(polytrack, p1);
osm_gps_map_track_add_point(polytrack, p2);
=====================================
catalog.xml → osmgpsmap.xml
=====================================
@@ -1,11 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
-<glade-catalog name="osmgpsmap" library="osmgpsmap-1.0" depends="gtk+" book="osm-gps-map">
+<glade-catalog name="osmgpsmap" library="osmgpsmap-1.0" depends="gtk+" book="osm-gps-map" supports="gtkbuilder">
<glade-widget-classes>
<glade-widget-class name="OsmGpsMap" generic-name="map" title="Map">
<signals>
- <signal id="point-added" />
- <signal id="key_press_event" />
- <signal id="point-changed" />
+ <signal id="changed" />
</signals>
</glade-widget-class>
</glade-widget-classes>
=====================================
scripts/releases-data.py
=====================================
@@ -0,0 +1,190 @@
+#!/usr/bin/env python3
+"""Write website/_data/releases.json from the GitHub Releases API.
+
+Download list is Releases only. Tag-only tags are omitted on purpose.
+News bullets come from the release body (`## Changes in …`), not from
+re-parsing NEWS in CI.
+"""
+
+from __future__ import annotations
+
+import argparse
+import json
+import os
+import re
+import sys
+import urllib.error
+import urllib.request
+from pathlib import Path
+
+CHANGES_HEAD = re.compile(r"^#{1,2} Changes in\b")
+BULLET = re.compile(r"^[\*\-] (.+)$")
+
+
+def _api(url: str, token: str | None) -> object:
+ headers = {
+ "Accept": "application/vnd.github+json",
+ "User-Agent": "osm-gps-map-pages",
+ }
+ if token:
+ headers["Authorization"] = f"Bearer {token}"
+ req = urllib.request.Request(url, headers=headers)
+ with urllib.request.urlopen(req, timeout=30) as resp:
+ return json.load(resp)
+
+
+def tar_asset(rel: dict) -> str | None:
+ for asset in rel.get("assets") or []:
+ name = asset.get("name") or ""
+ url = asset.get("browser_download_url") or ""
+ if name.endswith(".tar.gz") and url:
+ return str(url)
+ return None
+
+
+def archive_url(repo: str, tag: str, asset: str | None) -> str:
+ if asset:
+ return asset
+ return f"https://github.com/{repo}/archive/refs/tags/{tag}.tar.gz"
+
+
+def changes_from_body(body: str) -> list[str]:
+ items: list[str] = []
+ in_changes = False
+ for line in (body or "").splitlines():
+ if CHANGES_HEAD.match(line.strip()):
+ in_changes = True
+ continue
+ if in_changes and line.startswith("## "):
+ break
+ if not in_changes:
+ continue
+ b = BULLET.match(line.strip())
+ if b:
+ items.append(b.group(1).strip())
+ return items
+
+
+def fetch_releases(repo: str, token: str | None) -> list[dict]:
+ out: list[dict] = []
+ page = 1
+ while True:
+ data = _api(
+ f"https://api.github.com/repos/{repo}/releases?per_page=100&page={page}",
+ token,
+ )
+ if not isinstance(data, list) or not data:
+ break
+ for item in data:
+ if item.get("draft"):
+ continue
+ tag = item.get("tag_name")
+ if not tag:
+ continue
+ body = item.get("body") or ""
+ out.append(
+ {
+ "tag": str(tag),
+ "date": (item.get("published_at") or "")[:10],
+ "prerelease": bool(item.get("prerelease")),
+ "html_url": str(
+ item.get("html_url")
+ or f"https://github.com/{repo}/releases/tag/{tag}"
+ ),
+ "archive_url": archive_url(repo, str(tag), tar_asset(item)),
+ "changes": changes_from_body(body),
+ }
+ )
+ if len(data) < 100:
+ break
+ page += 1
+ return out
+
+
+def payload(repo: str, rels: list[dict]) -> dict:
+ published = [r for r in rels if not r["prerelease"]]
+ latest = published[0] if published else (rels[0] if rels else None)
+ return {
+ "repo": repo,
+ "clone_url": f"https://github.com/{repo}.git",
+ "master_tar": f"https://github.com/{repo}/archive/refs/heads/master.tar.gz",
+ "master_zip": f"https://github.com/{repo}/archive/refs/heads/master.zip",
+ "latest": latest,
+ "archives": rels,
+ }
+
+
+def main(argv: list[str] | None = None) -> int:
+ p = argparse.ArgumentParser()
+ p.add_argument("--repo", required=True)
+ p.add_argument("--out", type=Path, required=True)
+ p.add_argument("--token", default=os.environ.get("GITHUB_TOKEN", ""))
+ args = p.parse_args(argv)
+ try:
+ rels = fetch_releases(args.repo, args.token or None)
+ except urllib.error.HTTPError as exc:
+ raise SystemExit(f"releases API failed: {exc}") from exc
+ args.out.parent.mkdir(parents=True, exist_ok=True)
+ args.out.write_text(
+ json.dumps(payload(args.repo, rels), indent=2) + "\n",
+ encoding="utf-8",
+ )
+ return 0
+
+
+SAMPLE_BODY = """# osm-gps-map lab-1
+
+## Changes in lab-1
+
+- jekyll: boilerplate landing page
+- ci: Actions deploys Pages
+
+## Source tarball
+
+- File: `osm-gps-map-lab-1.tar.gz`
+"""
+
+SAMPLE_STAR_BODY = """# osm-gps-map 1.2.1
+
+## Changes in 1.2.1
+ * Replace deprecated G_TYPE_INSTANCE_GET_PRIVATE
+ * Add python unit tests (Patrick Salecker)
+"""
+
+
+def _self_check() -> None:
+ assert changes_from_body(SAMPLE_BODY) == [
+ "jekyll: boilerplate landing page",
+ "ci: Actions deploys Pages",
+ ]
+ assert changes_from_body(SAMPLE_STAR_BODY) == [
+ "Replace deprecated G_TYPE_INSTANCE_GET_PRIVATE",
+ "Add python unit tests (Patrick Salecker)",
+ ]
+ assert changes_from_body("no heading\n- nope") == []
+ data = payload(
+ "nzjrs/osm-gps-map",
+ [
+ {
+ "tag": "lab-1",
+ "date": "2026-09-10",
+ "prerelease": False,
+ "html_url": "https://github.com/nzjrs/osm-gps-map/releases/tag/lab-1",
+ "archive_url": archive_url("nzjrs/osm-gps-map", "lab-1", None),
+ "changes": ["jekyll: boilerplate landing page"],
+ }
+ ],
+ )
+ assert data["latest"]["tag"] == "lab-1"
+ assert len(data["archives"]) == 1
+ empty = payload("nzjrs/osm-gps-map", [])
+ assert empty["latest"] is None
+ assert empty["archives"] == []
+ print("releases-data self-check ok")
+
+
+if __name__ == "__main__":
+ if len(sys.argv) > 1 and sys.argv[1] == "--self-check":
+ _self_check()
+ else:
+ raise SystemExit(main())
=====================================
src/converter.c
=====================================
@@ -22,6 +22,7 @@
#include <math.h>
#include <stdio.h>
#include <float.h>
+#include <limits.h>
#include "private.h"
#include "converter.h"
@@ -126,7 +127,14 @@ latlon2zoom(int pix_height,
{
float lat1_m = atanh(sin(lat1));
float lat2_m = atanh(sin(lat2));
- int zoom_lon = LOG2((double)(2 * pix_width * M_PI) / (TILESIZE * (lon2 - lon1)));
- int zoom_lat = LOG2((double)(2 * pix_height * M_PI) / (TILESIZE * (lat2_m - lat1_m)));
+ float d_lon = lon2 - lon1;
+ float d_lat = lat2_m - lat1_m;
+ int zoom_lon = INT_MAX;
+ int zoom_lat = INT_MAX;
+
+ if (d_lon != 0.0f)
+ zoom_lon = LOG2((double)(2 * pix_width * M_PI) / (TILESIZE * d_lon));
+ if (d_lat != 0.0f)
+ zoom_lat = LOG2((double)(2 * pix_height * M_PI) / (TILESIZE * d_lat));
return MIN(zoom_lon, zoom_lat);
}
=====================================
src/osm-gps-map-layer.c
=====================================
@@ -24,8 +24,15 @@
* @include: osm-gps-map.h
*
* #OsmGpsMapLayer is an interface implemented by objects that wish
- * to draw on top of the map respond to button press events. The most
- * common implementation of this interface is #OsmGpsMapOsd
+ * to draw on top of the map and respond to button press events. The most
+ * common implementation of this interface is #OsmGpsMapOsd.
+ *
+ * The map already provides a cairo context in osm_gps_map_layer_draw().
+ * Convert lat/lon to widget pixels with
+ * osm_gps_map_convert_geographic_to_screen(). See DrawLayer in
+ * examples/mapviewer.py and examples/mapviewer.c. For tracks and images
+ * use osm_gps_map_track_add() and osm_gps_map_image_add() instead of a
+ * custom layer.
**/
#include "osm-gps-map-layer.h"
=====================================
src/osm-gps-map-layer.h
=====================================
@@ -66,7 +66,9 @@ GType osm_gps_map_layer_get_type (void);
* @self: (in): a #OsmGpsMapLayer object
* @map: (in): a #OsmGpsMap widget
*
- * Render layer on map
+ * Called when the offscreen map pixmap is rebuilt. Use this to update
+ * cached drawing (as #OsmGpsMapOsd does). Geographic overlays can leave
+ * this empty and paint in osm_gps_map_layer_draw() instead.
*
* Since: 0.6.0
**/
@@ -78,7 +80,11 @@ void osm_gps_map_layer_render (OsmGpsMapLayer *self, OsmGpsMap
* @map: (in): a #OsmGpsMap widget
* @cr: (in): a cairo context to draw to
*
- * Draw layer on map
+ * During #GtkWidget::draw the map passes its cairo context here. Draw in
+ * widget pixel coordinates. Convert geographic positions with
+ * osm_gps_map_convert_geographic_to_screen(). Do not create your own
+ * cairo surface; the map already has one. For GPS tracks and markers
+ * prefer osm_gps_map_track_add() and osm_gps_map_image_add().
*
* Since: 0.6.0
**/
@@ -103,7 +109,7 @@ gboolean osm_gps_map_layer_busy (OsmGpsMapLayer *self);
*
* Handle button event
*
- * Returns: whether even had been handled
+ * Returns: whether the event had been handled
* Since: 0.6.0
**/
gboolean osm_gps_map_layer_button_press (OsmGpsMapLayer *self, OsmGpsMap *map, GdkEventButton *event);
=====================================
src/osm-gps-map-track.c
=====================================
@@ -312,11 +312,16 @@ int osm_gps_map_track_n_points(OsmGpsMapTrack* track)
return g_slist_length(track->priv->track);
}
-void osm_gps_map_track_insert_point(OsmGpsMapTrack* track, OsmGpsMapPoint* np, int pos)
+void
+osm_gps_map_track_insert_point (OsmGpsMapTrack* track, OsmGpsMapPoint* np, int pos)
{
- OsmGpsMapTrackPrivate* priv = track->priv;
- priv->track = g_slist_insert(priv->track, np, pos);
- g_signal_emit(track, signals[POINT_INSERTED], 0, pos);
+ // TODO: const OsmGpsMapPoint * like add_point (1.3)
+ g_return_if_fail (OSM_GPS_MAP_IS_TRACK (track));
+ OsmGpsMapTrackPrivate *priv = track->priv;
+
+ OsmGpsMapPoint *p = g_boxed_copy (OSM_TYPE_GPS_MAP_POINT, np);
+ priv->track = g_slist_insert (priv->track, p, pos);
+ g_signal_emit (track, signals[POINT_INSERTED], 0, pos);
}
OsmGpsMapPoint* osm_gps_map_track_get_point(OsmGpsMapTrack* track, int pos)
=====================================
src/osm-gps-map-widget.c
=====================================
@@ -326,6 +326,13 @@ cached_tile_free (OsmCachedTile *tile)
g_slice_free (OsmCachedTile, tile);
}
+static gboolean
+unref_in_idle (gpointer data)
+{
+ g_object_unref (data);
+ return FALSE;
+}
+
/*
* Description:
* Find and replace text within a string.
@@ -744,13 +751,10 @@ osm_gps_map_tile_download_complete (SoupSession *session, GAsyncResult *result,
GError *error = NULL;
SoupMessage *msg = soup_session_get_async_result_message(session, result);
- SoupStatus soup_status = soup_message_get_status(msg);
+ SoupStatus soup_status = msg ? soup_message_get_status(msg) : 0;
GBytes *body = soup_session_send_and_read_finish (session, result, &error);
- GCancellable *cancellable = (GCancellable *)g_hash_table_lookup(priv->tile_queue, dl->uri);
- g_object_unref (cancellable);
-
- if (SOUP_STATUS_IS_SUCCESSFUL (soup_status)) {
+ if (!priv->is_disposed && body && SOUP_STATUS_IS_SUCCESSFUL (soup_status)) {
/* save tile into cachedir if one has been specified */
if (priv->cache_dir) {
if (g_mkdir_with_parents(dl->folder,0700) == 0) {
@@ -816,11 +820,7 @@ osm_gps_map_tile_download_complete (SoupSession *session, GAsyncResult *result,
}
g_hash_table_remove(priv->tile_queue, dl->uri);
g_object_notify(G_OBJECT(map), "tiles-queued");
-
- g_free(dl->folder);
- g_free(dl->filename);
- g_free(dl);
- } else {
+ } else if (!priv->is_disposed) {
if ((soup_status == SOUP_STATUS_NOT_FOUND) || (soup_status == SOUP_STATUS_FORBIDDEN)) {
g_hash_table_insert(priv->missing_tiles, dl->uri, NULL);
g_hash_table_remove(priv->tile_queue, dl->uri);
@@ -842,7 +842,15 @@ osm_gps_map_tile_download_complete (SoupSession *session, GAsyncResult *result,
}
}
-
+ if (body)
+ g_bytes_unref (body);
+ g_clear_error (&error);
+ g_free(dl->folder);
+ g_free(dl->filename);
+ g_free(dl);
+ /* Drop the download ref after this Soup callback returns. Unref from
+ * here disposes the session on the callback stack. */
+ g_idle_add (unref_in_idle, map);
}
static void
@@ -898,13 +906,14 @@ osm_gps_map_download_tile (OsmGpsMap *map, int zoom, int x, int y, gboolean redr
GCancellable *cancellable = g_cancellable_new ();
g_hash_table_insert (priv->tile_queue, dl->uri, cancellable);
- g_object_notify (G_OBJECT (map), "tiles-queued");
+ dl->map = g_object_ref (map);
/* the soup session unrefs the message when the download finishes */
soup_session_send_and_read_async(priv->soup_session, msg,
G_PRIORITY_DEFAULT,
cancellable,
(GAsyncReadyCallback)osm_gps_map_tile_download_complete,
dl);
+ g_object_notify (G_OBJECT (map), "tiles-queued");
} else {
g_warning("Could not create soup message");
g_free(dl->uri);
@@ -1111,10 +1120,17 @@ osm_gps_map_fill_tiles_pixel (OsmGpsMap *map, cairo_t *cr)
gtk_widget_get_allocation(GTK_WIDGET(map), &allocation);
- offset_x = - priv->map_x % TILESIZE;
- offset_y = - priv->map_y % TILESIZE;
- if (offset_x > 0) offset_x -= TILESIZE;
- if (offset_y > 0) offset_y -= TILESIZE;
+ /* C / truncates toward 0; tile index must floor so negative map_x/map_y
+ * stay aligned with overlays. Integer math keeps zoom 17-19 exact. */
+ tile_x0 = priv->map_x / TILESIZE;
+ tile_y0 = priv->map_y / TILESIZE;
+ if (priv->map_x < 0 && (priv->map_x % TILESIZE) != 0)
+ tile_x0--;
+ if (priv->map_y < 0 && (priv->map_y % TILESIZE) != 0)
+ tile_y0--;
+
+ offset_x = tile_x0 * TILESIZE - priv->map_x;
+ offset_y = tile_y0 * TILESIZE - priv->map_y;
offset_xn = offset_x + EXTRA_BORDER;
offset_yn = offset_y + EXTRA_BORDER;
@@ -1122,9 +1138,6 @@ osm_gps_map_fill_tiles_pixel (OsmGpsMap *map, cairo_t *cr)
tiles_nx = (allocation.width - offset_x) / TILESIZE + 1;
tiles_ny = (allocation.height - offset_y) / TILESIZE + 1;
- tile_x0 = floorf((float)priv->map_x / (float)TILESIZE);
- tile_y0 = floorf((float)priv->map_y / (float)TILESIZE);
-
for (i=tile_x0; i<(tile_x0+tiles_nx);i++)
{
for (j=tile_y0; j<(tile_y0+tiles_ny); j++)
@@ -1493,7 +1506,7 @@ osm_gps_map_map_redraw (OsmGpsMap *map)
/* clear white background */
w = gtk_widget_get_allocated_width (widget);
- h = gtk_widget_get_allocated_width (widget);
+ h = gtk_widget_get_allocated_height (widget);
draw_white_rectangle(cr, 0, 0, w + EXTRA_BORDER * 2, h + EXTRA_BORDER * 2);
osm_gps_map_fill_tiles_pixel(map, cr);
@@ -1716,10 +1729,9 @@ osm_gps_map_init (OsmGpsMap *object)
priv->soup_session = soup_session_new ();
soup_session_set_user_agent (priv->soup_session, USER_AGENT);
- /* Hash table which maps tile d/l URIs to SoupMessage requests, the hashtable
- must free the key, the soup session unrefs the message */
+ /* hash table maps tile download URIs to cancellables and owns both */
priv->tile_queue = g_hash_table_new_full (g_str_hash, g_str_equal,
- g_free, NULL);
+ g_free, g_object_unref);
//Some mapping providers (Google) have varying degrees of tiles at multiple
//zoom levels
@@ -2242,9 +2254,9 @@ osm_gps_map_button_press (GtkWidget *widget, GdkEventButton *event)
dist_sqrd = (event->x - ptx) * (event->x-ptx) + (event->y-pty) * (event->y-pty);
if(dist_sqrd <= ((DOT_RADIUS + 1) * (DOT_RADIUS + 1)))
{
- OsmGpsMapPoint* newpoint = malloc(sizeof(OsmGpsMapPoint));
- osm_gps_map_convert_screen_to_geographic(map, ptx, pty, newpoint);
- osm_gps_map_track_insert_point(track, newpoint, ctr);
+ OsmGpsMapPoint newpoint;
+ osm_gps_map_convert_screen_to_geographic(map, ptx, pty, &newpoint);
+ osm_gps_map_track_insert_point(track, &newpoint, ctr);
osm_gps_map_map_redraw(map);
return FALSE;
}
@@ -2301,9 +2313,9 @@ osm_gps_map_button_press (GtkWidget *widget, GdkEventButton *event)
dist_sqrd = (event->x - ptx) * (event->x-ptx) + (event->y-pty) * (event->y-pty);
if(dist_sqrd <= ((DOT_RADIUS + 1) * (DOT_RADIUS + 1)))
{
- OsmGpsMapPoint* newpoint = malloc(sizeof(OsmGpsMapPoint));
- osm_gps_map_convert_screen_to_geographic(map, ptx, pty, newpoint);
- osm_gps_map_track_insert_point(track, newpoint, ctr);
+ OsmGpsMapPoint newpoint;
+ osm_gps_map_convert_screen_to_geographic(map, ptx, pty, &newpoint);
+ osm_gps_map_track_insert_point(track, &newpoint, ctr);
osm_gps_map_map_redraw(map);
return FALSE;
}
@@ -2324,9 +2336,9 @@ osm_gps_map_button_press (GtkWidget *widget, GdkEventButton *event)
float dist_sqrd = (event->x - ptx) * (event->x-ptx) + (event->y-pty) * (event->y-pty);
if(dist_sqrd <= ((DOT_RADIUS + 1) * (DOT_RADIUS + 1)))
{
- OsmGpsMapPoint* newpoint = malloc(sizeof(OsmGpsMapPoint));
- osm_gps_map_convert_screen_to_geographic(map, ptx, pty, newpoint);
- osm_gps_map_track_insert_point(track, newpoint, ctr);
+ OsmGpsMapPoint newpoint;
+ osm_gps_map_convert_screen_to_geographic(map, ptx, pty, &newpoint);
+ osm_gps_map_track_insert_point(track, &newpoint, ctr);
osm_gps_map_map_redraw(map);
return FALSE;
}
=====================================
tests/test.py
=====================================
@@ -1,13 +1,17 @@
#!/usr/bin/env python3
+import os
+import tempfile
import unittest
import cairo
import io
import gi
gi.require_version('OsmGpsMap', '1.0')
+gi.require_version('Soup', '3.0')
+gi.require_foreign('cairo')
from gi.repository import OsmGpsMap
-from gi.repository import Gdk, GdkPixbuf, Gtk
+from gi.repository import Gdk, GdkPixbuf, GLib, GObject, Gtk, Soup
class TestOsmGpsMap(unittest.TestCase):
def setUp(self):
@@ -62,6 +66,54 @@ class TestOsmGpsMap(unittest.TestCase):
osd = OsmGpsMap.MapOsd(show_zoom=True, show_coordinates=False, show_scale=False, show_dpad=True, show_gps_in_dpad=True)
self.osm.layer_add(osd)
self.osm.layer_remove(osd)
+
+ def test_custom_layer_draw(self):
+ # MapLayer.do_draw gets the widget cairo_t; convert lat/lon in pixels.
+ class Layer(GObject.GObject, OsmGpsMap.MapLayer):
+ def __init__(self):
+ GObject.GObject.__init__(self)
+ self.drew = False
+ self.xy = None
+
+ def do_draw(self, gpsmap, cr):
+ pt = OsmGpsMap.MapPoint.new_degrees(50.0, 13.0)
+ x, y = gpsmap.convert_geographic_to_screen(pt)
+ cr.set_source_rgba(1, 0, 0, 1)
+ cr.arc(x, y, 4, 0, 6.28)
+ cr.fill()
+ self.xy = (x, y)
+ self.drew = True
+
+ def do_render(self, gpsmap):
+ pass
+
+ def do_busy(self):
+ return False
+
+ def do_button_press(self, gpsmap, event):
+ return False
+
+ GObject.type_register(Layer)
+ layer = Layer()
+ window = Gtk.Window()
+ window.set_size_request(320, 240)
+ window.add(self.osm)
+ self.osm.layer_add(layer)
+ self.osm.set_center_and_zoom(50.0, 13.0, 10)
+ window.show_all()
+
+ deadline = GLib.get_monotonic_time() + 500000
+ while GLib.get_monotonic_time() < deadline and not layer.drew:
+ Gtk.main_iteration_do(False)
+
+ self.assertTrue(layer.drew)
+ self.assertIsNotNone(layer.xy)
+ alloc = self.osm.get_allocation()
+ self.assertAlmostEqual(layer.xy[0], alloc.width / 2, delta=40)
+ self.assertAlmostEqual(layer.xy[1], alloc.height / 2, delta=40)
+ self.osm.layer_remove(layer)
+ window.remove(self.osm)
+ window.destroy()
def test_image(self):
size = 16
@@ -122,5 +174,120 @@ class TestOsmGpsMap(unittest.TestCase):
self.osm.track_remove(track)
+ def test_insert_point(self):
+ # Issue #45: check insert_point does not double-free.
+ track = OsmGpsMap.MapTrack()
+ point = OsmGpsMap.MapPoint.new_degrees(self.lat, self.lon)
+ track.insert_point(point, 0)
+ self.assertEqual(track.n_points(), 1)
+
+ def test_pending_tile_download_survives_map_teardown(self):
+ server = Soup.Server()
+ def serve(_server, message, _path, _query):
+ message.pause()
+ server.add_handler(None, serve)
+ server.listen_local(0, Soup.ServerListenOptions.IPV4_ONLY)
+ self.addCleanup(server.disconnect)
+ uri = server.get_uris()[0].to_string()
+ osm = OsmGpsMap.Map(user_agent="test/0.1",
+ repo_uri=uri + "#Z/#X/#Y.png",
+ tile_cache="none://")
+ criticals = []
+ def on_critical(_domain, _level, message, _data):
+ criticals.append(message)
+ for domain in ("GLib", "GLib-GObject", "Gtk", "OsmGpsMap"):
+ hid = GLib.log_set_handler(domain, GLib.LogLevelFlags.LEVEL_CRITICAL,
+ on_critical, None)
+ self.addCleanup(GLib.log_remove_handler, domain, hid)
+ window = Gtk.OffscreenWindow()
+ window.set_default_size(256, 256)
+ queued = []
+ osm.connect("notify::tiles-queued", lambda m, _p: queued.append(m.get_property("tiles-queued")))
+ window.add(osm)
+ window.show_all()
+ deadline = GLib.get_monotonic_time() + 1000000
+ while not queued and GLib.get_monotonic_time() < deadline:
+ Gtk.main_iteration_do(False)
+ self.assertTrue(queued)
+ released = []
+ osm.weak_ref(lambda: released.append(True))
+ window.destroy()
+ del osm
+ deadline = GLib.get_monotonic_time() + 1000000
+ while not released and GLib.get_monotonic_time() < deadline:
+ Gtk.main_iteration_do(False)
+ self.assertTrue(released, "Download callbacks did not release the map")
+ self.assertEqual(criticals, [])
+
+ def test_convert_screen_to_geographic(self):
+ # GI returns the MapPoint; do not pass one in.
+ pt = self.osm.convert_screen_to_geographic(0, 0)
+ self.assertEqual(type(pt), OsmGpsMap.MapPoint)
+ lat, lon = pt.get_degrees()
+ self.assertTrue(-90.0 <= lat <= 90.0)
+ self.assertTrue(-180.0 <= lon <= 180.0)
+
+ def test_zoom_fit_bbox_point(self):
+ # Degenerate bbox (one geotag). Must not crash; zoom clamps to max.
+ self.osm.zoom_fit_bbox(self.lat, self.lat, self.lon, self.lon)
+ self.assertEqual(self.osm.get_property('zoom'),
+ self.osm.get_property('max-zoom'))
+
+ def test_negative_map_origin_tiles_align_with_overlay(self):
+ # Issue #119: C / truncates toward 0. Negative map-x/map-y must floor so
+ # tiles stay locked to overlays. Zoom 1, 800x800 window, center 0,0
+ # makes both origins negative. Red cached tiles vs white outside the world.
+ cache_dir = tempfile.TemporaryDirectory(prefix="osm-gps-map-tiles-")
+ self.addCleanup(cache_dir.cleanup)
+ cache = cache_dir.name
+ tile = cairo.ImageSurface(cairo.FORMAT_RGB24, 256, 256)
+ cr = cairo.Context(tile)
+ cr.set_source_rgb(1, 0, 0)
+ cr.paint()
+ for x in (0, 1):
+ for y in (0, 1):
+ path = os.path.join(cache, "1", str(x), "%d.png" % y)
+ os.makedirs(os.path.dirname(path), exist_ok=True)
+ tile.write_to_png(path)
+
+ osm = OsmGpsMap.Map(user_agent="test/0.1",
+ tile_cache=cache,
+ auto_download=False)
+ window = Gtk.OffscreenWindow()
+ self.addCleanup(window.destroy)
+ window.set_default_size(800, 800)
+ window.add(osm)
+ self.addCleanup(window.remove, osm)
+ window.show_all()
+ osm.set_center_and_zoom(0.0, 0.0, 1)
+
+ deadline = GLib.get_monotonic_time() + 1000000
+ r = g = b = None
+ while GLib.get_monotonic_time() < deadline:
+ Gtk.main_iteration_do(False)
+ if osm.get_property("map-x") >= 0 or osm.get_property("map-y") >= 0:
+ continue
+ pixbuf = window.get_pixbuf()
+ if pixbuf is None or pixbuf.get_width() < 800 or pixbuf.get_height() < 800:
+ continue
+ pt = OsmGpsMap.MapPoint.new_degrees(0.0, 0.0)
+ sx, sy = osm.convert_geographic_to_screen(pt)
+ pixels = pixbuf.get_pixels()
+ nch = pixbuf.get_n_channels()
+ row = pixbuf.get_rowstride()
+ i = int(sy) * row + int(sx) * nch
+ r, g, b = pixels[i], pixels[i + 1], pixels[i + 2]
+ if r + g + b > 0:
+ break
+
+ self.assertLess(osm.get_property("map-x"), 0)
+ self.assertNotEqual(osm.get_property("map-x") % 256, 0)
+ self.assertLess(osm.get_property("map-y"), 0)
+ self.assertNotEqual(osm.get_property("map-y") % 256, 0)
+ self.assertIsNotNone(r)
+ self.assertGreater(r, 200)
+ self.assertLess(g, 50)
+ self.assertLess(b, 50)
+
if __name__ == "__main__":
unittest.main()
=====================================
website/Gemfile
=====================================
@@ -0,0 +1,5 @@
+source "https://rubygems.org"
+
+gem "jekyll", "~> 4.4"
+gem "jekyll-theme-midnight", "~> 0.2"
+gem "jekyll-seo-tag", "~> 2.8"
=====================================
website/Gemfile.lock
=====================================
@@ -0,0 +1,170 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ addressable (2.9.0)
+ public_suffix (>= 2.0.2, < 8.0)
+ base64 (0.3.0)
+ bigdecimal (4.1.2)
+ colorator (1.1.0)
+ concurrent-ruby (1.3.8)
+ csv (3.3.6)
+ em-websocket (0.5.3)
+ eventmachine (>= 0.12.9)
+ http_parser.rb (~> 0)
+ eventmachine (1.2.7)
+ ffi (1.17.4)
+ ffi (1.17.4-aarch64-linux-gnu)
+ ffi (1.17.4-aarch64-linux-musl)
+ ffi (1.17.4-arm-linux-gnu)
+ ffi (1.17.4-arm-linux-musl)
+ ffi (1.17.4-arm64-darwin)
+ ffi (1.17.4-x86-linux-gnu)
+ ffi (1.17.4-x86-linux-musl)
+ ffi (1.17.4-x86_64-darwin)
+ ffi (1.17.4-x86_64-linux-gnu)
+ ffi (1.17.4-x86_64-linux-musl)
+ forwardable-extended (2.6.0)
+ google-protobuf (4.36.1)
+ bigdecimal
+ rake (~> 13.3)
+ google-protobuf (4.36.1-aarch64-linux-gnu)
+ bigdecimal
+ rake (~> 13.3)
+ google-protobuf (4.36.1-aarch64-linux-musl)
+ bigdecimal
+ rake (~> 13.3)
+ google-protobuf (4.36.1-arm64-darwin)
+ bigdecimal
+ rake (~> 13.3)
+ google-protobuf (4.36.1-x86-linux-gnu)
+ bigdecimal
+ rake (~> 13.3)
+ google-protobuf (4.36.1-x86-linux-musl)
+ bigdecimal
+ rake (~> 13.3)
+ google-protobuf (4.36.1-x86_64-darwin)
+ bigdecimal
+ rake (~> 13.3)
+ google-protobuf (4.36.1-x86_64-linux-gnu)
+ bigdecimal
+ rake (~> 13.3)
+ google-protobuf (4.36.1-x86_64-linux-musl)
+ bigdecimal
+ rake (~> 13.3)
+ http_parser.rb (0.8.1)
+ i18n (1.15.2)
+ concurrent-ruby (~> 1.0)
+ jekyll (4.4.1)
+ addressable (~> 2.4)
+ base64 (~> 0.2)
+ colorator (~> 1.0)
+ csv (~> 3.0)
+ em-websocket (~> 0.5)
+ i18n (~> 1.0)
+ jekyll-sass-converter (>= 2.0, < 4.0)
+ jekyll-watch (~> 2.0)
+ json (~> 2.6)
+ kramdown (~> 2.3, >= 2.3.1)
+ kramdown-parser-gfm (~> 1.0)
+ liquid (~> 4.0)
+ mercenary (~> 0.3, >= 0.3.6)
+ pathutil (~> 0.9)
+ rouge (>= 3.0, < 5.0)
+ safe_yaml (~> 1.0)
+ terminal-table (>= 1.8, < 4.0)
+ webrick (~> 1.7)
+ jekyll-sass-converter (3.1.0)
+ sass-embedded (~> 1.75)
+ jekyll-seo-tag (2.9.0)
+ jekyll (>= 3.8, < 5.0)
+ jekyll-theme-midnight (0.2.0)
+ jekyll (> 3.5, < 5.0)
+ jekyll-seo-tag (~> 2.0)
+ jekyll-watch (2.2.1)
+ listen (~> 3.0)
+ json (2.21.2)
+ kramdown (2.5.2)
+ rexml (>= 3.4.4)
+ kramdown-parser-gfm (1.1.0)
+ kramdown (~> 2.0)
+ liquid (4.0.4)
+ listen (3.10.0)
+ logger
+ rb-fsevent (~> 0.10, >= 0.10.3)
+ rb-inotify (~> 0.9, >= 0.9.10)
+ logger (1.7.0)
+ mercenary (0.4.0)
+ pathutil (0.16.2)
+ forwardable-extended (~> 2.6)
+ public_suffix (7.0.5)
+ rake (13.4.2)
+ rb-fsevent (0.11.2)
+ rb-inotify (0.11.1)
+ ffi (~> 1.0)
+ rexml (3.4.4)
+ rouge (4.7.0)
+ safe_yaml (1.0.5)
+ sass-embedded (1.104.0)
+ google-protobuf (~> 4.31)
+ rake (>= 13)
+ sass-embedded (1.104.0-aarch64-linux-android)
+ google-protobuf (~> 4.31)
+ sass-embedded (1.104.0-aarch64-linux-gnu)
+ google-protobuf (~> 4.31)
+ sass-embedded (1.104.0-aarch64-linux-musl)
+ google-protobuf (~> 4.31)
+ sass-embedded (1.104.0-arm-linux-androideabi)
+ google-protobuf (~> 4.31)
+ sass-embedded (1.104.0-arm-linux-gnueabihf)
+ google-protobuf (~> 4.31)
+ sass-embedded (1.104.0-arm-linux-musleabihf)
+ google-protobuf (~> 4.31)
+ sass-embedded (1.104.0-arm64-darwin)
+ google-protobuf (~> 4.31)
+ sass-embedded (1.104.0-riscv64-linux-android)
+ google-protobuf (~> 4.31)
+ sass-embedded (1.104.0-riscv64-linux-gnu)
+ google-protobuf (~> 4.31)
+ sass-embedded (1.104.0-riscv64-linux-musl)
+ google-protobuf (~> 4.31)
+ sass-embedded (1.104.0-x86_64-darwin)
+ google-protobuf (~> 4.31)
+ sass-embedded (1.104.0-x86_64-linux-android)
+ google-protobuf (~> 4.31)
+ sass-embedded (1.104.0-x86_64-linux-gnu)
+ google-protobuf (~> 4.31)
+ sass-embedded (1.104.0-x86_64-linux-musl)
+ google-protobuf (~> 4.31)
+ terminal-table (3.0.2)
+ unicode-display_width (>= 1.1.1, < 3)
+ unicode-display_width (2.6.0)
+ webrick (1.9.2)
+
+PLATFORMS
+ aarch64-linux-android
+ aarch64-linux-gnu
+ aarch64-linux-musl
+ arm-linux-androideabi
+ arm-linux-gnu
+ arm-linux-gnueabihf
+ arm-linux-musl
+ arm-linux-musleabihf
+ arm64-darwin
+ riscv64-linux-android
+ riscv64-linux-gnu
+ riscv64-linux-musl
+ ruby
+ x86-linux-gnu
+ x86-linux-musl
+ x86_64-darwin
+ x86_64-linux-android
+ x86_64-linux-gnu
+ x86_64-linux-musl
+
+DEPENDENCIES
+ jekyll (~> 4.4)
+ jekyll-seo-tag (~> 2.8)
+ jekyll-theme-midnight (~> 0.2)
+
+BUNDLED WITH
+ 2.6.9
=====================================
website/_config.yml
=====================================
@@ -0,0 +1,19 @@
+title: osm-gps-map
+tagline: A Gtk+ Widget for Displaying OpenStreetMap tiles.
+description: A Gtk+ widget (and Python bindings) for displaying OpenStreetMap tiles.
+theme: jekyll-theme-midnight
+url: https://nzjrs.github.io
+baseurl: /osm-gps-map
+show_downloads: true
+plugins:
+ - jekyll-seo-tag
+github:
+ repository_url: https://github.com/nzjrs/osm-gps-map
+ owner_url: https://github.com/nzjrs
+ owner_name: nzjrs
+ zip_url: https://github.com/nzjrs/osm-gps-map/archive/refs/heads/master.zip
+ tar_url: https://github.com/nzjrs/osm-gps-map/archive/refs/heads/master.tar.gz
+exclude:
+ - Gemfile
+ - Gemfile.lock
+ - vendor
=====================================
website/_data/releases.json
=====================================
@@ -0,0 +1,8 @@
+{
+ "repo": "nzjrs/osm-gps-map",
+ "clone_url": "https://github.com/nzjrs/osm-gps-map.git",
+ "master_tar": "https://github.com/nzjrs/osm-gps-map/archive/refs/heads/master.tar.gz",
+ "master_zip": "https://github.com/nzjrs/osm-gps-map/archive/refs/heads/master.zip",
+ "latest": null,
+ "archives": []
+}
=====================================
website/index.md
=====================================
@@ -0,0 +1,133 @@
+---
+layout: default
+permalink: /
+---
+
+{% if site.data.releases.latest %}
+<p>Download <a href="{{ site.data.releases.latest.archive_url }}">{{ site.data.releases.latest.tag }} archive</a>
+(<a href="{{ site.data.releases.latest.html_url }}">Release notes for {{ site.data.releases.latest.tag }}</a>).</p>
+{% else %}
+<p>No GitHub Release yet. Tag-only publishes gtk-doc; Download fills in when a Release exists.</p>
+{% endif %}
+
+A Gtk+ widget (and Python bindings) that when given GPS co-ordinates, draws a GPS track, and points of interest on a moving map display. Downloads map data from a number of websites, including openstreetmap.org. The library has excellent performance and is currently used in a number of Gtk+ applications.
+
+
+
+
+
+Currently supports a number of different mapping sources:
+
+- OpenStreetMap (default)
+- OpenTopoMap
+- OpenCycleMap
+- Maps-For-Free
+- Google Maps / Satellite and Bing Maps (Virtual Earth)
+
+It also has the following features:
+
+- Intelligent, flexible and customizable caching of maps, including the ability to request a specific area of the map to be cached ahead of time
+- Recording of points of interest on the map (and the ability to add arbitrary pixmaps at those points)
+- Automatically draws a GPS track (a line showing the history of past added points)
+- Automatic centering on new GPS points
+- Support for multiple other tracks of co-ordinate points
+- Adjustable Zoom
+- Built in support for keyboard navigation
+- Includes a [set of examples](https://github.com/{{ site.data.releases.repo }}/tree/master/examples/)
+- Simple, [flat API](https://github.com/{{ site.data.releases.repo }}/blob/master/src/osm-gps-map-widget.h)
+- Support for showing additional display layers rendered on top of the map
+- Optional on screen display (OSD)
+- Proper map copyright attribution and user-agent usage to comply with various map requirements
+
+## Documentation
+
+- [API Documentation]({{ '/docs/reference/html/index.html' | relative_url }})
+- Code examples
+ - [mapviewer.c](https://github.com/{{ site.data.releases.repo }}/blob/master/examples/mapviewer.c)
+ - [mapviewer.py](https://github.com/{{ site.data.releases.repo }}/blob/master/examples/mapviewer.py)
+ - [mapviewer.js](https://github.com/{{ site.data.releases.repo }}/blob/master/examples/mapviewer.js) (JavaScript example using GObject Introspection)
+ - [polygon.c](https://github.com/{{ site.data.releases.repo }}/blob/master/examples/polygon.c) (small polygon-on-map drawing example)
+ - [editable_track.c](https://github.com/{{ site.data.releases.repo }}/blob/master/examples/editable_track.c) (small example of an editable track feature)
+- Please e-mail the [mailing list](https://groups.google.com/group/osm-gps-map) for all questions.
+- Report issues using the canonical [issue tracker](https://github.com/nzjrs/osm-gps-map/issues)
+
+## Install Instructions
+
+osm-gps-map should be packaged by your distribution. On Debian, Ubuntu or similar, you can install the GTK+ library and its Python bindings like this:
+
+```
+sudo apt install libosmgpsmap-1.0-1 gir1.2-osmgpsmap-1.0 python3-gi
+```
+
+And if you want to build some application that depends on osm-gps-map, you can install the development files like this:
+
+```
+sudo apt install libosmgpsmap-1.0-dev
+```
+
+### Installing From Source
+
+To build from source on Debian or Ubuntu you will need:
+
+```
+sudo apt install build-essential autoconf automake libtool \
+ libgtk-3-dev libsoup-3.0-dev libgirepository1.0-dev
+```
+
+Then:
+
+```
+./autogen.sh && make && sudo make install
+```
+
+{% if site.data.releases.latest and site.data.releases.latest.changes.size > 0 %}
+## News
+
+<p>Changes in {{ site.data.releases.latest.tag }} ({{ site.data.releases.latest.date }})</p>
+<ul>
+{% for item in site.data.releases.latest.changes %}
+ <li>{{ item }}</li>
+{% endfor %}
+</ul>
+{% endif %}
+
+## Download
+
+You may download any of the following archives:
+
+{% if site.data.releases.archives.size > 0 %}
+<ul>
+{% for rel in site.data.releases.archives %}
+ <li><a href="{{ rel.archive_url }}">{{ rel.tag }} archive</a> (<a href="{{ rel.html_url }}">Release notes for {{ rel.tag }}</a>)</li>
+{% endfor %}
+</ul>
+{% else %}
+<ul><li>(no GitHub Releases yet)</li></ul>
+{% endif %}
+
+You can also clone the project with [Git](https://git-scm.com) by running:
+
+```
+$ git clone {{ site.data.releases.clone_url }}
+```
+
+A snapshot of master is also available as [tar]({{ site.data.releases.master_tar }}) or [zip]({{ site.data.releases.master_zip }}).
+
+## License
+
+- GPLv2+
+
+## Authors
+
+- John Stowers
+- Till Harbaum
+- Alberto Mardegan
+- Mark Cottrell
+- Originally based on tangoGPS by Marcus Bauer
+
+## Contact
+
+- osm-gps-map [mailing list](https://groups.google.com/group/osm-gps-map)
+- Canonical [issue tracker](https://github.com/nzjrs/osm-gps-map/issues)
+
+get the source code on GitHub : [{{ site.data.releases.repo }}](https://github.com/{{ site.data.releases.repo }})
=====================================
website/screenshot-osd.png
=====================================
Binary files /dev/null and b/website/screenshot-osd.png differ
=====================================
website/screenshot.png
=====================================
Binary files /dev/null and b/website/screenshot.png differ
View it on GitLab: https://salsa.debian.org/debian-gis-team/osm-gps-map/-/commit/04511e21b5faf9568ac3e143863526926ae51b50
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/osm-gps-map/-/commit/04511e21b5faf9568ac3e143863526926ae51b50
You're receiving this email because of your account on salsa.debian.org. Manage all notifications: https://salsa.debian.org/-/profile/notifications | Help: https://salsa.debian.org/help
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20261012/0b570c8a/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list