branch: externals/osm
commit 7e78000a7a2937f479c25f6c1e97d838f16104b5
Author: Daniel Mendler <[email protected]>
Commit: Daniel Mendler <[email protected]>

    Add osm-route command
---
 osm.el | 138 ++++++++++++++++++++++++++++++++++++++++++++---------------------
 1 file changed, 93 insertions(+), 45 deletions(-)

diff --git a/osm.el b/osm.el
index 9f77e55b17..298c84e1a0 100644
--- a/osm.el
+++ b/osm.el
@@ -34,8 +34,8 @@
 ;; multiple preconfigured tile servers.  You can bookmark your favorite
 ;; locations using regular Emacs bookmarks or create links from Org files
 ;; to locations.  Furthermore the package provides commands to measure
-;; distances, search for locations by name and to open and display GPX
-;; tracks.
+;; distances, search for locations and routes by name and to open and
+;; display GPX tracks.
 
 ;; osm.el requires Emacs 29 and depends on the external `curl' program.
 ;; Emacs must be built with libxml, libjansson, librsvg, libjpeg, libpng
@@ -266,6 +266,7 @@ Should be at least 7 days according to the server usage 
policies."
   "t" #'osm-goto
   "u" #'osm-url
   "j" #'osm-jump
+  "r" #'osm-route
   "x" #'osm-gpx-show
   "X" #'osm-gpx-hide)
 
@@ -336,9 +337,11 @@ Should be at least 7 days according to the server usage 
policies."
     ["Go to coordinates" osm-goto]
     ["Go to URL" osm-url]
     ["Jump to pin" osm-jump]
-    ["Search by name" osm-search]
     ["Change tile server" osm-server]
     "--"
+    ["Search by name" osm-search]
+    ["Plan route" osm-route]
+    "--"
     ["Org Link" org-store-link]
     ["Geo URL" osm-save-url]
     ["Elisp Link" (osm-save-url t)]
@@ -467,15 +470,39 @@ Local per buffer since the overlays depend on the zoom 
level.")
   (setq lat (* lat (/ float-pi 180.0)))
   (- 0.5 (/ (log (+ (tan lat) (/ 1.0 (cos lat)))) float-pi 2)))
 
-(defun osm--boundingbox-to-zoom (lat1 lat2 lon1 lon2)
-  "Compute zoom level from boundingbox LAT1 to LAT2 and LON1 to LON2."
-  (let ((w (/ (frame-pixel-width) 256))
-        (h (/ (frame-pixel-height) 256)))
+(defun osm--bb-to-zoom (bb)
+  "Zoom level from bounding box BB."
+  (pcase-let ((`(,min-lat ,max-lat ,min-lon ,max-lon) bb)
+              (w (/ (frame-pixel-width) 256))
+              (h (/ (frame-pixel-height) 256)))
     (max (osm--server-property :min-zoom)
          (min
           (osm--server-property :max-zoom)
-          (min (logb (/ w (abs (- (osm--lon-to-normalized-x lon1) 
(osm--lon-to-normalized-x lon2)))))
-               (logb (/ h (abs (- (osm--lat-to-normalized-y lat1) 
(osm--lat-to-normalized-y lat2))))))))))
+          (min (logb (/ w (abs (- (osm--lon-to-normalized-x min-lon)
+                                  (osm--lon-to-normalized-x max-lon)))))
+               (logb (/ h (abs (- (osm--lat-to-normalized-y min-lat)
+                                  (osm--lat-to-normalized-y max-lat))))))))))
+
+(defun osm--bb-center (bb)
+  "Center of bounding box BB."
+  (pcase-let ((`(,min-lat ,max-lat ,min-lon ,max-lon) bb))
+    (cons (/ (+ min-lat max-lat) 2) (/ (+ min-lon max-lon) 2))))
+
+(defun osm--bb-from-track (track waypoints)
+  "Compute bounding box from TRACK and WAYPOINTS."
+  (let ((min-lat 90) (max-lat -90) (min-lon 180) (max-lon -180))
+    (cl-loop for seg in track do
+             (cl-loop for (lat . lon) in seg do
+                      (setq min-lat (min lat min-lat)
+                            max-lat (max lat max-lat)
+                            min-lon (min lon min-lon)
+                            max-lon (max lon max-lon))))
+    (cl-loop for (lat lon . _) in waypoints do
+             (setq min-lat (min lat min-lat)
+                   max-lat (max lat max-lat)
+                   min-lon (min lon min-lon)
+                   max-lon (max lon max-lon)))
+    (list min-lat max-lat min-lon max-lon)))
 
 (defun osm--x-to-lon (x zoom)
   "Return longitude in degrees for X/ZOOM."
@@ -730,7 +757,7 @@ Local per buffer since the overlays depend on the zoom 
level.")
                (length osm--track) (+ len1 len2)
                (if (or (= len1 0) (= len2 0))
                    sel-name
-                 (format "%.2fkm → %s → %.2fkm"
+                 (format "%.2fkm ⟶ %s ⟶ %.2fkm"
                          len1 sel-name len2))))))
 
 (defun osm--pin-at (event &optional type)
@@ -1738,9 +1765,35 @@ See `osm-search-server' and `osm-search-language' for 
customization."
   (let ((selected (osm--search-select needle lucky)))
     ;; TODO: Add search bounded to current viewbox, bounded=1, 
viewbox=x1,y1,x2,y2
     (osm--goto (cadr selected) (caddr selected)
-               (apply #'osm--boundingbox-to-zoom (cdddr selected))
+               (osm--bb-to-zoom (cdddr selected))
                nil 'osm-selected (car selected))))
 
+;;;###autoload
+(defun osm-route ()
+  "Fetch a route between two locations."
+  (interactive)
+  (let* ((from (osm--search-select (osm--search-read "From: ") nil))
+         (to (osm--search-select (osm--search-read "To: ") nil))
+         (by (completing-read "By: " '("car" "bike" "foot") nil t nil t))
+         (data
+          (progn
+            ;; TODO make this configurable, use `format-spec' for url params
+            (message "Contacting routing.openstreetmap.de")
+            (osm--fetch-json
+             (format 
"https://routing.openstreetmap.de/routed-%s/route/v1/driving/%.6f,%.6f;%.6f,%.6f?steps=false&overview=full&alternatives=false&geometries=geojson";
+                     by (caddr from) (cadr from) (caddr to) (cadr to)))))
+         (route (car (alist-get 'routes data)))
+         (coords (or (alist-get 'coordinates (alist-get 'geometry route))
+                     (error "No route available")))
+         (waypoints (alist-get 'waypoints data)))
+    (osm--add-gpx
+     (format "By %s: %s ⟶ %s" by (car from) (car to))
+     (list (mapcar (lambda (x) (cons (cadr x) (car x))) coords))
+     (mapcar (lambda (x)
+               (let ((l (alist-get 'location x)))
+                 (list (cadr l) (car l) (alist-get 'name x))))
+             waypoints))))
+
 ;;;###autoload
 (defun osm-gpx-show (file)
   "Show the tracks of gpx FILE in an `osm-mode' buffer."
@@ -1748,45 +1801,40 @@ See `osm-search-server' and `osm-search-language' for 
customization."
   (osm--check-libraries)
   (let ((dom (with-temp-buffer
                (insert-file-contents file)
-               (libxml-parse-xml-region (point-min) (point-max))))
-        (min-lat 90) (max-lat -90) (min-lon 180) (max-lon -180))
+               (libxml-parse-xml-region (point-min) (point-max)))))
     (unless (eq 'gpx (dom-tag dom))
       (setq dom (dom-child-by-tag dom 'gpx)))
     (unless (and dom (eq 'gpx (dom-tag dom)))
       (error "Not a GPX file"))
-    (setf (alist-get (abbreviate-file-name file) osm--gpx-files nil nil 
#'equal)
-          (cons
-           (cl-loop
-            for trk in (dom-children dom)
-            if (eq (dom-tag trk) 'trk) nconc
-            (cl-loop
-             for seg in (dom-children trk)
-             if (eq (dom-tag seg) 'trkseg) collect
-             (cl-loop
-              for pt in (dom-children seg)
-              if (eq (dom-tag pt) 'trkpt) collect
-              (let ((lat (string-to-number (dom-attr pt 'lat)))
-                    (lon (string-to-number (dom-attr pt 'lon))))
-                (setq min-lat (min lat min-lat)
-                      max-lat (max lat max-lat)
-                      min-lon (min lon min-lon)
-                      max-lon (max lon max-lon))
-                (cons lat lon)))))
-           (cl-loop
-            for pt in (dom-children dom)
-            if (eq (dom-tag pt) 'wpt) collect
-            (let ((lat (string-to-number (dom-attr pt 'lat)))
-                  (lon (string-to-number (dom-attr pt 'lon))))
-              (setq min-lat (min lat min-lat)
-                    max-lat (max lat max-lat)
-                    min-lon (min lon min-lon)
-                    max-lon (max lon max-lon))
-              (list lat lon (with-no-warnings
-                              (dom-text (dom-child-by-tag pt 'name))))))))
+    (osm--add-gpx
+     (abbreviate-file-name file)
+     (cl-loop
+      for trk in (dom-children dom)
+      if (eq (dom-tag trk) 'trk) nconc
+      (cl-loop
+       for seg in (dom-children trk)
+       if (eq (dom-tag seg) 'trkseg) collect
+       (cl-loop
+        for pt in (dom-children seg)
+        if (eq (dom-tag pt) 'trkpt) collect
+        (cons (string-to-number (dom-attr pt 'lat))
+              (string-to-number (dom-attr pt 'lon))))))
+     (cl-loop
+      for pt in (dom-children dom)
+      if (eq (dom-tag pt) 'wpt) collect
+      (list (string-to-number (dom-attr pt 'lat))
+            (string-to-number (dom-attr pt 'lon))
+            (with-no-warnings
+              (dom-text (dom-child-by-tag pt 'name))))))))
+
+(defun osm--add-gpx (name track waypoints)
+  "Add GPX track with NAME consisting of TRACK and WAYPOINTS."
+  (let* ((bb (osm--bb-from-track track waypoints))
+         (center (osm--bb-center bb)))
+    (setf (alist-get name osm--gpx-files nil nil #'equal)
+          (cons track waypoints))
     (osm--revert)
-    (osm--goto (/ (+ min-lat max-lat) 2) (/ (+ min-lon max-lon) 2)
-               (osm--boundingbox-to-zoom min-lat max-lat min-lon max-lon)
-               nil nil nil)))
+    (osm--goto (car center) (cdr center) (osm--bb-to-zoom bb) nil nil nil)))
 
 (defun osm-gpx-hide (file)
   "Show the tracks of gpx FILE in an `osm-mode' buffer."

Reply via email to