I like drawing things on a map whether it is for planning a trip, marking the most interesting path on a trek, or mapping favorite restaurants. One of the best libraries for drawing and interacting with world maps is OpenLayers which I used for countless projects. Most of the code I wrote to display something on a world map is the same, boilerplate so naturally I decided to create a library that will abstract some of that stuff. This library is cljs-map-pins.

cljs-map-pins is a ClojureScript library that utilizes shadow-cljs for interacting with OpenLayers. It allows the user to specify a list of features to draw on a map via an EDN file (or normal vector). Each feature has a coordinates vector to mark the location on the world map and basic information about it. Normally, I would want to click on the feature and have something happen in the DOM like display a pop-up with an explanation or show a preview panel. This is supported by the library in the form of specifying an ‘on-click’ handler function for the feature type. And that’s it! This is everything that cljs-map-pins does.

Here is an example of the library being used:

(require '[goog.dom :as gdom])
(require '[cljs-map-pins.core :as mp])
(require '["ol/control" :as olc :refer (Zoom)])
(require '["ol/style" :as ols :refer (Style RegularShape Fill Stroke)])

;; reference the DOM element where the map will be drawn
(def map-element (gdom/getElement "map"))

;; OpenLayers map options
(def ol-options
  {:center [20.459526 44.815500]
   :extent [18.5 40.4 22.7 47.5]
   :zoom 10
   :minZoom 9
   :maxZoom 17
   :projection "EPSG:4326"
   :controls [(olc/Zoom.)]})

;; a vector of features that will be drawn on the OL map
(def features
  [{:name "Feature 1"
    :type :normal
    :title "Map pin"
    :description "This is the first feature on the map."
    :url ""
    :coordinates [20.459526 44.815500]
    :img "img/pin.png"}
   {:name "Feature 2"
    :type :special
    :title "Map triangle shape"
    :description "This is the second feature on the map."
    :coordinates [20.450938469037453 44.8224132937195]
    :style (ols/Style. #js {:image (ols/RegularShape. #js {:fill (ols/Fill. #js {:color "red"})
                                                           :stroke (ols/Stroke. #js {:color "black"
                                                                                     :width 2})
                                                           :points 3
                                                           :radius 10
                                                           :rotation (/ (.-PI js/Math) 4)
                                                           :angle 0})})}])

;; the default on-click handler which is an instance of FeatureHandler
(def default-type-handler
  (reify mp/FeatureHandler
    (handle [this feature]
      (do-something-in-dom! feature))))

;; the on-click handler for features of type :special
(def special-type-handler
  (reify mp/FeatureHandler
    (handle [this feature]
      (do-something-special-in-dom! feature))))

;; the no-op function that will be called on on-click event without a feature
(defn noop-handler

;; The mapping of handlers to feature types
;; each feature type should be mapped to a handler instance,
;; and if not, the default handler will be used.
;; The reserved keywords are :default which maps to the
;; default handler instance, and :no-feature wich maps
;; to the function that will be called when an 'on-click' events
;; happens on a map part without a feature.
(def handlers
  {:normal default-type-handler
   :special special-type-handler
   :default default-type-handler
   :no-feature noop-handler})

;; function for drawing the map which returns an instance of ol.Map JS object
(def ol-map (mp/draw-pins! map-element ol-options features handlers))

Plenty more information can be found on the project page.

You can see some of the projects that inspired this library (and that will be updated to use it) here and here.