| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487 |
- b(in-package :cl-user)
- (defpackage #:ocm.osm
- (:use :cl :alexandria))
- (in-package :ocm.osm)
- (defun next-power-of-two (number)
- (ash 1 (ceiling (log number 2))))
- (defvar *read-buffer* (proto:make-byte-vector 4096))
- (defun ensure-buffer (size)
- (when (> size (array-total-size *read-buffer*))
- (setf *read-buffer* (proto:make-byte-vector (next-power-of-two size)))))
- (defmacro read-big-endian (stream &optional (sizeof 4))
- (with-gensyms (i unsigned-value)
- `(let ((,unsigned-value 0))
- (dotimes (,i ,sizeof ,unsigned-value)
- (setf ,unsigned-value (+ (* ,unsigned-value #x100)
- (read-byte ,stream)))))))
- (defmacro read-little-endian (stream &optional (sizeof 4))
- (with-gensyms (i unsigned-value)
- `(let ((,unsigned-value 0))
- (dotimes (,i ,sizeof ,unsigned-value)
- (setf ,unsigned-value (+ ,unsigned-value
- (ash (read-byte ,stream )
- (* 8 ,i))))))))
- (defvar *stream* nil "PBF file stream")
- (defvar *blob-header-size* nil "Current header size during file parsing")
- (defvar *blob-header* nil "Current header")
- (defvar *blob-size* nil "Current blob size during file parsing")
- (defvar *blob-type* nil "Current blob type")
- (defvar *blob* nil "Current blob")
- (defvar *block-size* nil "Current block size")
- (defvar *header-block* nil "File header block")
- (defvar *primitive-block* nil "Current primitive block")
- (defparameter +blob-type-osm-header+ "OSMHeader")
- (defparameter +blob-type-osm-data+ "OSMData")
- (defun read-proto (type size &optional no-read)
- (unless no-read
- (ensure-buffer size)
- (read-sequence *read-buffer* *stream* :end size))
- (proto:deserialize-object type *read-buffer* 0 size))
- (defmacro iterate-osm ((filespec) &body body)
- `(with-open-file (*stream* ,filespec :element-type 'unsigned-byte)
- (handler-case
- (loop
- (let* ((*blob-header* (read-proto 'osmpbf:blob-header (read-big-endian *stream* 4)))
- (*blob-type* (osmpbf:type *blob-header*))
- (*blob-size* (osmpbf:datasize *blob-header*)))
- (if (or (equal *blob-type* +blob-type-osm-data+)
- (equal *blob-type* +blob-type-osm-header+))
- (let* ((*blob* (read-proto 'osmpbf:blob *blob-size*))
- (*block-size* (unpack-block *blob*)))
- (if (equal *blob-type* +blob-type-osm-header+)
- (ensure-features (read-proto 'osmpbf:header-block *block-size* t))
- (let ((*primitive-block* (read-proto 'osmpbf:primitive-block *block-size* t)))
- ,@body)))
- (file-position *stream* (+ (file-position *stream*) *blob-size*)))))
- (end-of-file (e)
- (declare (ignore e))))))
- (defun degran-lat (lat block)
- (+ (osmpbf:lat-offset block)
- (* lat (osmpbf:granularity block))))
- (defun degran-lon (lon block)
- (+ (osmpbf:lon-offset block)
- (* lon (osmpbf:granularity block))))
- (defun sid (indexes string-table)
- (loop for i in indexes
- collect (trivial-utf-8:utf-8-bytes-to-string (elt string-table i))))
- (defmacro string-table (block)
- `(osmpbf:s (osmpbf:stringtable ,block)))
- (defmacro unsid (object accessor block)
- `(setf (,accessor ,object)
- (sid (,accessor ,object)
- (string-table ,block))))
- (defun unsid2 (sids string-table)
- (loop for i from 0
- for sid in sids
- do (setf (elt sids i)
- (trivial-utf-8:utf-8-bytes-to-string (elt string-table sid)))))
- (defmacro iterate-nodes ((node block) &body body)
- (with-gensyms (group info)
- `(loop for ,group in (osmpbf:primitivegroup ,block)
- do (loop for ,node in (osmpbf:nodes ,group)
- do (progn
- (unsid ,node osmpbf:keys ,block)
- (unsid ,node osmpbf:vals ,block)
- (when-let (,info (osmpbf:info )))
- (setf (osmpbf:lat ,node) (degran-lat (osmpbf:lat ,node) ,block)
- (osmpbf:lon ,node) (degran-lon (osmpbf:lon ,node) ,block))
- ,@body)))))
- (defmacro iterate-dense-nodes ((node block) &body body)
- (with-gensyms (group string-table dense dense-info id lat lon tag-start tag-end info ith key val)
- `(loop for ,group in (osmpbf:primitivegroup ,block)
- with ,string-table = (osmpbf:s (osmpbf:stringtable ,block))
- do (when-let (,dense (osmpbf:dense ,group))
- (undelta (osmpbf:id ,dense))
- (undelta (osmpbf:lat ,dense))
- (undelta (osmpbf:lon ,dense))
- (when-let (,dense-info (osmpbf:denseinfo ,dense))
- (undelta (osmpbf:timestamp ,dense-info))
- (undelta (osmpbf:changeset ,dense-info))
- (undelta (osmpbf:uid ,dense-info))
- (undelta (osmpbf:user-sid ,dense-info)))
- (loop
- for ,id in (osmpbf:id ,dense)
- for ,lat in (osmpbf:lat ,dense)
- for ,lon in (osmpbf:lon ,dense)
- for ,tag-start = 0 then ,tag-end
- for ,tag-end = (position 0 (osmpbf:keys-vals ,dense) :start ,tag-start)
- for ,ith from 0
- do (let* ((,info (when-let (,dense-info (osmpbf:denseinfo ,dense))
- (make-instance 'osmpbf:info
- :version (elt (osmpbf:version ,dense-info) ,ith)
- :timestamp (elt (osmpbf:timestamp ,dense-info) ,ith)
- :changeset (elt (osmpbf:changeset ,dense-info) ,ith)
- :uid (elt (osmpbf:uid ,dense-info) ,ith)
- :user-sid (car (sid (list (elt (osmpbf:user-sid ,dense-info) ,ith))
- ,string-table)))))
- (,node (make-instance 'osmpbf:node
- :id ,id
- :keys (sid (loop for ,key
- in (subseq (osmpbf:keys-vals ,dense)
- ,tag-start ,tag-end)
- by #'cddr
- collect ,key)
- ,string-table)
- :vals (sid (loop for ,val
- in (subseq (osmpbf:keys-vals ,dense)
- (1+ ,tag-start) ,tag-end)
- by #'cddr
- collect ,val)
- ,string-table)
- :lat (degran-lat ,lat ,block)
- :lon (degran-lon ,lon ,block)
- :info ,info)))
- ,@body))))))
- (defmacro iterate-dense ((id lat lon keys vals block) &body body)
- (with-gensyms (group string-table dense tag-start tag-end key val id-cur lat-cur lon-cur id-prev lat-prev lon-prev lat-acc lon-acc)
- `(loop for ,group in (osmpbf:primitivegroup ,block)
- with ,string-table = (string-table ,block)
- do (when-let (,dense (osmpbf:dense ,group))
- (loop
- for ,id-cur in (osmpbf:id ,dense)
- for ,lat-cur in (osmpbf:lat ,dense)
- for ,lon-cur in (osmpbf:lon ,dense)
- for ,id-prev = 0 then ,id
- for ,lat-prev = 0 then ,lat-acc
- for ,lon-prev = 0 then ,lon-acc
- for ,tag-start = 0 then ,tag-end
- for ,tag-end = (position 0 (osmpbf:keys-vals ,dense) :start ,tag-start)
- for ,id = (+ ,id-cur ,id-prev)
- for ,lat-acc = (+ ,lat-cur ,lat-prev)
- for ,lon-acc = (+ ,lon-cur ,lon-prev)
- for ,lat = (degran-lat ,lat-acc ,block)
- for ,lon = (degran-lon ,lon-acc ,block)
- do (multiple-value-bind (,keys ,vals)
- (loop for (,key ,val) on (subseq (osmpbf:keys-vals ,dense) ,tag-start ,tag-end) by #'cddr
- collecting ,key into ,keys
- collecting ,val into ,vals
- finally (return (values ,keys ,vals)))
- (let ((,keys (sid ,keys ,string-table))
- (,vals (sid ,vals ,string-table)))
- ,@body)))))))
- (defmacro iterate-ways ((way block) &body body)
- (with-gensyms (group)
- `(loop for ,group in (osmpbf:primitivegroup ,block)
- do (loop for ,way in (osmpbf:ways ,group)
- do (progn
- (unsid ,way osmpbf:keys ,block)
- (unsid ,way osmpbf:vals ,block)
- (undelta (osmpbf:refs ,way))
- ,@body)))))
- (defmacro iterate-rels ((rel block) &body body)
- (with-gensyms (group string-table)
- `(loop for ,group in (osmpbf:primitivegroup ,block)
- with ,string-table = (string-table ,block)
- do (loop for ,rel in (osmpbf:relations ,group)
- do (progn
- (unsid2 (osmpbf:keys ,rel) ,string-table)
- (unsid2 (osmpbf:vals ,rel) ,string-table)
- (unsid2 (osmpbf:roles-sid ,rel) ,string-table)
- (undelta (osmpbf:memids ,rel))
- ,@body)))))
- (defun get-tags (object string-table)
- (loop for key in (osmpbf:keys object)
- for val in (osmpbf:vals object)
- collect (cons (trivial-utf-8:utf-8-bytes-to-string (elt string-table key))
- (trivial-utf-8:utf-8-bytes-to-string (elt string-table val)))))
- (defun delta-decode (seq)
- (loop for elt in seq
- for prev = 0 then cur
- for cur = (+ elt prev)
- collect cur))
- (defmacro iterate-dense2 ((id tags lat lon block) &body body)
- (with-gensyms (group string-table dense tag-start tag-end key val id-cur lat-cur lon-cur id-prev lat-prev lon-prev lat-acc lon-acc key-vals clo-start clo-end)
- `(loop for ,group in (osmpbf:primitivegroup ,block)
- with ,string-table = (string-table ,block)
- do (when-let (,dense (osmpbf:dense ,group))
- (loop
- with ,key-vals = (osmpbf:keys-vals ,dense)
- for ,id-cur in (osmpbf:id ,dense)
- for ,lat-cur in (osmpbf:lat ,dense)
- for ,lon-cur in (osmpbf:lon ,dense)
- for ,id-prev = 0 then ,id
- for ,lat-prev = 0 then ,lat-acc
- for ,lon-prev = 0 then ,lon-acc
- for ,tag-start = 0 then ,tag-end
- for ,tag-end = (position 0 ,key-vals :start ,tag-start)
- for ,id = (+ ,id-cur ,id-prev)
- for ,lat-acc = (+ ,lat-cur ,lat-prev)
- for ,lon-acc = (+ ,lon-cur ,lon-prev)
- for ,lat = (degran-lat ,lat-acc ,block)
- for ,lon = (degran-lon ,lon-acc ,block)
- for ,tags = (unless (= ,tag-start ,tag-end)
- (let ((,clo-start ,tag-start)
- (,clo-end ,tag-end))
- (lambda ()
- (loop for (,key ,val) on (subseq ,key-vals ,clo-start ,clo-end) by #'cddr
- collect (cons (trivial-utf-8:utf-8-bytes-to-string
- (elt ,string-table ,key))
- (trivial-utf-8:utf-8-bytes-to-string
- (elt ,string-table ,val)))))))
- do (progn ,@body))))))
- (defmacro iterate-ways2 ((id tags refs block) &body body)
- (with-gensyms (group string-table object o-string-table)
- `(loop for ,group in (osmpbf:primitivegroup ,block)
- with ,string-table = (string-table ,block)
- do (loop for ,object in (osmpbf:ways ,group)
- for ,id = (osmpbf:id ,object)
- for ,o-string-table = ,string-table
- for ,tags = (lambda () (get-tags ,object ,o-string-table))
- for ,refs = (lambda () (delta-decode (osmpbf:refs ,object)))
- do (progn ,@body)))))
- (defmacro iterate-rels2 ((id tags roles memids types block) &body body)
- (with-gensyms (group string-table relation)
- `(loop for ,group in (osmpbf:primitivegroup ,block)
- with ,string-table = (string-table ,block)
- do (loop for ,relation in (osmpbf:relations ,group)
- for ,id = (osmpbf:id ,relation)
- for ,tags = (lambda () (get-tags ,relation ,string-table))
- for ,roles = (lambda () (sid (osmpbf:roles-sid ,relation) ,string-table))
- for ,memids = (lambda () (delta-decode (osmpbf:memids ,relation)))
- for ,types = (lambda () (osmpbf:types ,relation))
- do (progn ,@body)))))
- (defun undelta (seq)
- (when (> (length seq) 1)
- (loop for i from 1 below (length seq)
- do (incf (elt seq i)
- (elt seq (1- i)))))
- seq)
- (defparameter +supported-features+ '("OsmSchema-V0.6" "DenseNodes"))
- (defun ensure-features (header)
- (when-let (missing-features (set-difference (osmpbf:required-features header)
- +supported-features+
- :test 'equal))
- (error "Features ~A not supported" missing-features))
- (setf *header-block* header))
- (defun unpack-block (blob)
- (if-let (uncompressed (osmpbf:raw blob))
- (let ((size (array-total-size uncompressed)))
- (ensure-buffer size)
- (dotimes (i (array-total-size uncompressed))
- (setf (row-major-aref *read-buffer* i)
- (row-major-aref uncompressed i)))
- size)
- (if-let (zlib-data (osmpbf:zlib-data blob))
- (let ((size (osmpbf:raw-size blob)))
- (ensure-buffer size)
- (chipz:decompress *read-buffer* (chipz:make-dstate :zlib) zlib-data)
- size)
- (error "Unsupported block compression"))))
- (defun type-match (type-expr type)
- (etypecase type-expr
- (null t)
- (symbol (eql type-expr type))
- (list (member type type-expr))))
- (defun expr-match (template value)
- (etypecase template
- (null t)
- (string (equal template value))
- (list (destructuring-bind (type expr) template
- (let ((pos (search expr value)))
- (ecase type
- (:pre (eql pos 0))
- (:suf (eql pos (max 0 (- (length value)
- (length expr)))))
- (:inf (not (null pos)))
- (:eql (and (eql pos 0)
- (= (length value)
- (length expr))))))))))
- (defun query-match (query type keys vals)
- (or (null query)
- (loop for expr in query
- when (destructuring-bind (tp &optional keyp valp) expr
- (and
- (type-match tp type)
- (or (and (null keyp) (null valp))
- (loop for key in keys
- for val in vals
- when (and (expr-match keyp key)
- (expr-match valp val))
- return t))))
- return t)))
- (defun query-match2 (query type tags-thunk)
- (or (null query)
- (loop for expr in query
- when (destructuring-bind (tp &optional keyp valp) expr
- (and
- (type-match tp type)
- (or (and (null keyp) (null valp))
- (and tags-thunk
- (loop for (key . val) in (if (functionp tags-thunk) (funcall tags-thunk) tags-thunk)
- when (and (expr-match keyp key)
- (expr-match valp val))
- return t)))))
- return t)))
- (defun query-has-type (query type)
- (or (null query)
- (loop for expr in query
- when (type-match (car expr) type)
- return t)))
- (defmacro iterate ((object filespec tags-query) &body body)
- (let ((has-rels (query-has-type (cadr tags-query) :rel))
- (has-ways (query-has-type (cadr tags-query) :way))
- (has-nodes (query-has-type (cadr tags-query) :node)))
- (with-gensyms (id lat lon keys vals query)
- `(let ((,query ,tags-query))
- (iterate-osm (,filespec)
- ,@(when has-rels `((iterate-rels (,object *primitive-block*)
- (when (query-match ,query :rel (osmpbf:keys ,object) (osmpbf:vals ,object))
- ,@body))))
- ,@(when has-ways `((iterate-ways (,object *primitive-block*)
- (when (query-match ,query :way (osmpbf:keys ,object) (osmpbf:vals ,object))
- ,@body))))
- ,@(when has-nodes
- `((iterate-nodes (,object *primitive-block*)
- (when (query-match ,query :node (osmpbf:keys ,object) (osmpbf:vals ,object))
- ,@body))
- (iterate-dense (,id ,lat ,lon ,keys ,vals *primitive-block*)
- (when (query-match ,query :node ,keys ,vals)
- (let ((,object (make-instance 'osmpbf:node :id ,id :lat ,lat :lon ,lon
- :keys ,keys :vals ,vals)))
- ,@body))))))))))
- (defstruct obj id tags)
- (defstruct (node (:include obj)) lat lon)
- (defstruct (way (:include obj)) refs)
- (defstruct (rel (:include obj)) roles memids types)
- (defmacro iterate2 ((object filespec query) &body body)
- (let ((has-rels (query-has-type (cadr query) :rel))
- (has-ways (query-has-type (cadr query) :way))
- (has-nodes (query-has-type (cadr query) :node)))
- (with-gensyms (g-query g-id g-tags g-roles g-memids g-types g-refs g-lat g-lon)
- `(let ((,g-query ,query))
- (iterate-osm (,filespec)
- ,@(when has-rels `((iterate-rels2 (,g-id ,g-tags ,g-roles ,g-memids ,g-types *primitive-block*)
- (when (query-match2 ,g-query :rel ,g-tags)
- (let ((,object (make-rel :id ,g-id :tags ,g-tags
- :roles ,g-roles :memids ,g-memids :types ,g-types)))
- ,@body)))))
- ,@(when has-ways `((iterate-ways2 (,g-id ,g-tags ,g-refs *primitive-block*)
- (when (query-match2 ,g-query :way ,g-tags)
- (let ((,object (make-way :id ,g-id :tags ,g-tags
- :refs ,g-refs)))
- ,@body)))))
- ,@(when has-nodes `((iterate-dense2 (,g-id ,g-tags ,g-lat ,g-lon *primitive-block*)
- (when (query-match2 ,g-query :node ,g-tags)
- (let ((,object (make-node :id ,g-id :tags ,g-tags
- :lat ,g-lat :lon ,g-lon)))
- ,@body))))))))))
- (defparameter +max-rel-pass+ 5 "max rel-2-rel passes")
- (defmacro with-related ((object filespec query) &body body)
- (with-gensyms (g-object g-filespec g-objects g-idx-rels g-idx-ways g-idx-nodes g-rel g-type g-memid g-indexed
- g-dirty g-try g-indexed-list)
- `(let ((,g-filespec ,filespec)
- ,g-objects
- (,g-idx-rels (make-hash-table))
- (,g-idx-ways (make-hash-table))
- (,g-idx-nodes (make-hash-table)))
- (print "iterate queried")
- (iterate2 (,g-object ,g-filespec ,query)
- (typecase ,g-object
- (node (let ((,object ,g-object)) ,@body))
- (way (push ,g-object ,g-objects)
- (setf (obj-tags ,g-object) (funcall (obj-tags ,g-object))
- (way-refs ,g-object) (funcall (way-refs ,g-object)))
- (loop for ,g-rel in (way-refs ,g-object)
- do (push ,g-object (gethash ,g-rel ,g-idx-nodes))))
- (rel (push ,g-object ,g-objects)
- (setf (obj-tags ,g-object) (funcall (obj-tags ,g-object))
- (rel-roles ,g-object) (funcall (rel-roles ,g-object))
- (rel-memids ,g-object) (funcall (rel-memids ,g-object))
- (rel-types ,g-object) (funcall (rel-types ,g-object)))
- (loop for ,g-memid in (rel-memids ,g-object)
- for ,g-type in (rel-types ,g-object)
- do (push ,g-object (gethash ,g-memid (ecase ,g-type
- (:node ,g-idx-nodes)
- (:way ,g-idx-ways)
- (:relation ,g-idx-rels))))))))
- (when (plusp (hash-table-count ,g-idx-rels))
- (loop
- for ,g-dirty = nil
- for ,g-try from 1 upto +max-rel-pass+
- do (format t "iterate rels pass ~a~%" ,g-try)
- do (iterate2 (,g-object ,g-filespec '((:rel)))
- (when-let ((,g-indexed-list (gethash (obj-id ,g-object) ,g-idx-rels)))
- (setf (rel-memids ,g-object) (funcall (rel-memids ,g-object))
- (rel-types ,g-object) (funcall (rel-types ,g-object)))
- (loop for ,g-indexed in ,g-indexed-list
- do (loop
- for ,g-memid on (rel-memids ,g-indexed)
- for ,g-type in (rel-types ,g-indexed)
- when (and (eql ,g-type :relation)
- (equal (car ,g-memid) (obj-id ,g-object)))
- do (setf (car ,g-memid) ,g-object) and ;; replace memid ref with current rel
- do (loop for ,g-memid in (rel-memids ,g-object) ;; add it's references to index
- for ,g-type in (rel-types ,g-object)
- do (push ,g-object
- (gethash ,g-memid (ecase ,g-type
- (:node ,g-idx-nodes)
- (:way ,g-idx-ways)
- (:relation (prog1 ,g-idx-rels
- (setf ,g-dirty t)))))))))
- (remhash (obj-id ,g-object) ,g-idx-rels))) ;; remove processed rel from index
- while ,g-dirty))
- (when (plusp (hash-table-count ,g-idx-ways))
- (print "iterate ways")
- (iterate2 (,g-object ,g-filespec '((:way)))
- (when-let ((,g-indexed-list (gethash (obj-id ,g-object) ,g-idx-ways)))
- (setf (way-refs ,g-object) (funcall (way-refs ,g-object)))
- (loop for ,g-indexed in ,g-indexed-list
- do (loop
- for ,g-memid on (rel-memids ,g-indexed)
- for ,g-type in (rel-types ,g-indexed)
- when (and (eql ,g-type :way)
- (equal (car ,g-memid) (obj-id ,g-object)))
- do (setf (car ,g-memid) ,g-object) and ;; replace memid ref with current way
- do (loop for ,g-rel in (way-refs ,g-object) ;; add it's refs to index
- do (push ,g-object (gethash ,g-rel ,g-idx-nodes)))))
- (remhash (obj-id ,g-object) ,g-idx-ways)))) ;; remove processed way from index
- (when (plusp (hash-table-count ,g-idx-nodes))
- (print "iterate nodes")
- (iterate2 (,g-object ,g-filespec '((:node)))
- (when-let ((,g-indexed-list (gethash (obj-id ,g-object) ,g-idx-nodes)))
- (loop for ,g-indexed in ,g-indexed-list
- do (etypecase ,g-indexed
- (rel (loop
- for ,g-memid on (rel-memids ,g-indexed)
- for ,g-type in (rel-types ,g-indexed)
- when (and (eql ,g-type :node)
- (equal (car ,g-memid) (obj-id ,g-object)))
- do (setf (car ,g-memid) ,g-object))) ;; replace rel's memid with current node
- (way (nsubstitute ,g-object (obj-id ,g-object) (way-refs ,g-indexed))))) ;; replace way's ref with current node
- (remhash (obj-id ,g-object) ,g-idx-nodes))))
- (loop for ,object in (nreverse ,g-objects) ;; Iterate original matched objects
- do (progn ,@body)))))
|