Skip to content

Commit

Permalink
Add support for using Open Street Map on the Map and Group pages.
Browse files Browse the repository at this point in the history
The map is displayed using https://leafletjs.com/

To enable Open Street Map go to the Admin -> Configuration -> Content section and click 'Open Street Map Enabled'.

The 'Open Street Map Provider Name' setting can be used to configure different tile providers thanks to the Leaflet-providers extension (https://github.com/leaflet-extras/leaflet-providers)

Some tile providers require an API key, this can provided in JSON format e.g. '{ apiKey: 123 }' in the 'Open Street Map Provider Options' setting.

Each tile provider has their own usage policy so this should be checked before enabling Open Street Map.

The search field for the Open Street Map works differently than searching on Google Maps. It matches producers by their name or address because it was easier to implement instead of matching place names all over the world.
  • Loading branch information
cillian committed May 8, 2020
1 parent b9c86d5 commit 87427e4
Show file tree
Hide file tree
Showing 19 changed files with 1,852 additions and 19 deletions.
3 changes: 3 additions & 0 deletions app/assets/javascripts/darkswarm/all.js.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
#= require angular-sanitize
#= require angular-animate
#= require angular-resource
#= require autocomplete.min.js
#= require leaflet-1.6.0.js
#= require leaflet-providers.js
#= require lodash.underscore.js
# bluebird.js is a dependency of angular-google-maps.js 2.0.0
#= require bluebird.js
Expand Down
100 changes: 100 additions & 0 deletions app/assets/javascripts/darkswarm/directives/open_street_map.js.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
Darkswarm.directive 'ofnOpenStreetMap', ($window, Enterprises, EnterpriseModal, availableCountries, openStreetMapConfig) ->
restrict: 'E'
replace: true
scope: true
template: "<div></div>"

link: (scope, element, attrs, ctrl, transclude)->
map = null
markers = []
enterpriseNames = []
openStreetMapProviderName = openStreetMapConfig.open_street_map_provider_name
openStreetMapProviderOptions = JSON.parse(openStreetMapConfig.open_street_map_provider_options)

average = (values) ->
total = values.reduce (sum, value) ->
sum = sum + value
, 0
total / values.length

averageAngle = (angleName) ->
positiveAngles = []
negativeAngles = []
for enterprise in Enterprises.enterprises
if enterprise.latitude? && enterprise.longitude?
if enterprise[angleName] > 0
positiveAngles.push(enterprise[angleName])
else
negativeAngles.push(enterprise[angleName])

averageNegativeAngle = average(negativeAngles)
averagePositiveAngle = average(positiveAngles)

if negativeAngles.length == 0
averagePositiveAngle
else if positiveAngles.length == 0
averageNegativeAngle
else if averagePositiveAngle > averageNegativeAngle
averagePositiveAngle - averageNegativeAngle
else
averageNegativeAngle - averagePositiveAngle

buildMarker = (enterprise, latlng, title) ->
icon = L.icon
iconUrl: enterprise.icon
marker = L.marker latlng,
draggable: true,
icon: icon,
riseOnHover: true,
title: title
marker.on "click", ->
EnterpriseModal.open enterprise
marker

enterpriseName = (enterprise) ->
return enterprise.name + " (" + enterprise.address.address1 + ", " + enterprise.address.city + ", " + enterprise.address.state_name + ")";

goToEnterprise = (selectedEnterpriseName) ->
enterprise = Enterprises.enterprises.find (enterprise) ->
enterpriseName(enterprise) == selectedEnterpriseName
map.setView([enterprise.latitude, enterprise.longitude], 12)

displayMap = ->
setMapDimensions()
averageLatitude = averageAngle("latitude")
averageLongitude = averageAngle("longitude")
zoomLevel = 6
map = L.map('open-street-map')
L.tileLayer.provider(openStreetMapProviderName, openStreetMapProviderOptions).addTo(map)
map.setView([averageLatitude, averageLongitude], zoomLevel)

displayEnterprises = ->
for enterprise in Enterprises.enterprises
if enterprise.latitude? && enterprise.longitude?
marker = buildMarker(enterprise, { lat: enterprise.latitude, lng: enterprise.longitude }, enterprise.name).addTo(map)
enterpriseNames.push(enterpriseName(enterprise))
markers.push(marker)

displaySearchField = () ->
new Autocomplete('#open-street-map--search',
onSubmit: goToEnterprise
search: searchEnterprises
)
overwriteInlinePositionRelativeToPositionSearchField = ->
$('#open-street-map--search').css("position", "absolute")
overwriteInlinePositionRelativeToPositionSearchField()

searchEnterprises = (input) ->
if input.length < 1
return []
enterpriseNames.filter (country) ->
country.toLowerCase().includes input.toLowerCase()

setMapDimensions = ->
height = $window.innerHeight - element.offset().top
element.css "width", "100%"
element.css "height", (height + "px")

displayMap()
displayEnterprises()
displaySearchField()
3 changes: 3 additions & 0 deletions app/assets/stylesheets/darkswarm/all.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
* the top of the compiled file, but it's generally better to create a new file per style scope.
*= require autocomplete
*= require leaflet
*= require_self
*/
@import 'variables';
Expand Down
22 changes: 22 additions & 0 deletions app/assets/stylesheets/darkswarm/map.css.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

.map-container {
width: 100%;
position: relative;

map, .angular-google-map-container, google-map, .angular-google-map {
display: block;
Expand Down Expand Up @@ -37,6 +38,27 @@
background: rgba(255, 255, 255, 1);
}
}

#open-street-map {
z-index: 1;
}

#open-street-map--search {
top: 16px;
left: 54px;
width: 50%;
z-index: 1000;
.autocomplete-input,
.autocomplete-result-list {
border: 2px solid #888;
&:hover, &:active, &:focus {
border-color: $clr-brick;
}
}
.autocomplete-result-list {
border-top: 1px dotted #888;
}
}
}

.map-footer {
Expand Down
3 changes: 2 additions & 1 deletion app/controllers/admin/contents_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ def preference_sections
PreferenceSections::GroupSignupPageSection.new,
PreferenceSections::MainLinksSection.new,
PreferenceSections::FooterAndExternalLinksSection.new,
PreferenceSections::UserGuideSection.new
PreferenceSections::UserGuideSection.new,
PreferenceSections::MapSection.new
]
end
end
Expand Down
4 changes: 4 additions & 0 deletions app/helpers/injection_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ def inject_currency_config
inject_json_ams "currencyConfig", {}, Api::CurrencyConfigSerializer
end

def inject_open_street_map_config
inject_json_ams "openStreetMapConfig", {}, Api::OpenStreetMapConfigSerializer
end

def inject_spree_api_key
render partial: "json/injection_ams", locals: { name: 'spreeApiKey', json: "'#{@spree_api_key}'" }
end
Expand Down
5 changes: 5 additions & 0 deletions app/models/content_configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ class ContentConfiguration < Spree::Preferences::FileConfiguration
preference :home_show_stats, :boolean, default: true
has_attached_file :home_hero, default_url: "/assets/home/home.jpg"

# Map
preference :open_street_map_enabled, :boolean, default: false
preference :open_street_map_provider_name, :string, default: "OpenStreetMap.Mapnik"
preference :open_street_map_provider_options, :text, default: "{}"

# Producer sign-up page
# All the following defaults using I18n don't work.
# https://github.com/openfoodfoundation/openfoodnetwork/issues/3816
Expand Down
15 changes: 15 additions & 0 deletions app/models/preference_sections/map_section.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module PreferenceSections
class MapSection
def name
I18n.t('admin.contents.edit.map')
end

def preferences
[
:open_street_map_enabled,
:open_street_map_provider_name,
:open_street_map_provider_options
]
end
end
end
15 changes: 15 additions & 0 deletions app/serializers/api/open_street_map_config_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class Api::OpenStreetMapConfigSerializer < ActiveModel::Serializer
attributes :open_street_map_enabled, :open_street_map_provider_name, :open_street_map_provider_options

def open_street_map_enabled
ContentConfig.open_street_map_enabled
end

def open_street_map_provider_name
ContentConfig.open_street_map_provider_name
end

def open_street_map_provider_options
ContentConfig.open_street_map_provider_options.to_json
end
end
21 changes: 13 additions & 8 deletions app/views/groups/show.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
= @group.logo.url

- content_for :injection_data do
= inject_available_countries
= inject_group_enterprises
= inject_open_street_map_config

#group-page.row.pad-top.footer-pad{"ng-controller" => "GroupPageCtrl"}
.small-12.columns.pad-top
Expand All @@ -32,13 +34,16 @@
%tab{heading: t(:label_map),
active: "tabs.map.active",
select: "select(\'map\')"}
.map-container
%map{"ng-if" => "(isActive(\'/map\') && (mapShowed = true)) || mapShowed"}
%ui-gmap-google-map{options: "map.additional_options", center: "map.center", zoom: "map.zoom", styles: "map.styles", draggable: "true"}
%map-osm-tiles
%map-search
%ui-gmap-markers{models: "mapMarkers", fit: "true",
coords: "'self'", icon: "'icon'", click: "'reveal'"}
.map-container{"ng-if" => "(isActive(\'/map\') && (mapShowed = true)) || mapShowed"}
- if ContentConfig.open_street_map_enabled
= render partial: 'map/open_street_map'
- else
%map
%ui-gmap-google-map{options: "map.additional_options", center: "map.center", zoom: "map.zoom", styles: "map.styles", draggable: "true"}
%map-osm-tiles
%map-search
%ui-gmap-markers{models: "mapMarkers", fit: "true",
coords: "'self'", icon: "'icon'", click: "'reveal'"}
%tab{heading: t(:groups_about),
active: "tabs.about.active",
Expand Down Expand Up @@ -122,4 +127,4 @@
%span
= t 'title'

= render "shared/footer"
= render "shared/footer"
3 changes: 2 additions & 1 deletion app/views/layouts/darkswarm.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
= render "layouts/bugsnag_js"
%script{:src => "https://js.stripe.com/v3/", :type => "text/javascript"}
%script{src: "//maps.googleapis.com/maps/api/js?libraries=places,geometry#{ ENV['GOOGLE_MAPS_API_KEY'] ? '&key=' + ENV['GOOGLE_MAPS_API_KEY'] : ''} "}
- if !ContentConfig.open_street_map_enabled
%script{src: "//maps.googleapis.com/maps/api/js?libraries=places,geometry#{ ENV['GOOGLE_MAPS_API_KEY'] ? '&key=' + ENV['GOOGLE_MAPS_API_KEY'] : ''} "}
= javascript_include_tag "darkswarm/all"
= javascript_include_tag "web/all"
= render "layouts/i18n_script"
Expand Down
4 changes: 4 additions & 0 deletions app/views/map/_open_street_map.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
%ofn-open-street-map#open-street-map
%div#open-street-map--search
%input.autocomplete-input
%ul.autocomplete-result-list
19 changes: 10 additions & 9 deletions app/views/map/index.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@
= t :label_map

- content_for :injection_data do
= inject_available_countries
= inject_enterprise_shopfront_list
= inject_open_street_map_config

.map-container{"fill-vertical" => true}
%map{"ng-controller" => "MapCtrl"}
%ui-gmap-google-map{options: "map.additional_options", center: "map.center", zoom: "map.zoom", styles: "map.styles", draggable: "true"}
%map-osm-tiles
%map-search
%ui-gmap-markers{models: "OfnMap.enterprises", fit: "true",
coords: "'self'", icon: "'icon'", click: "'reveal'"}
.map-footer
%a{:href => "http://www.openstreetmap.org/copyright"} &copy; OpenStreetMap contributors
- if ContentConfig.open_street_map_enabled
= render partial: 'map/open_street_map'
- else
%map{"ng-controller" => "MapCtrl"}
%ui-gmap-google-map{options: "map.additional_options", center: "map.center", zoom: "map.zoom", styles: "map.styles", draggable: "true"}
%map-osm-tiles
%map-search
%ui-gmap-markers{models: "OfnMap.enterprises", fit: "true", coords: "'self'", icon: "'icon'", click: "'reveal'"}
1 change: 1 addition & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ en:
footer_and_external_links: Footer and External Links
your_content: Your content
user_guide: User Guide
map: Map

enterprise_fees:
index:
Expand Down
1 change: 1 addition & 0 deletions vendor/assets/javascripts/autocomplete.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions vendor/assets/javascripts/leaflet-1.6.0.js

Large diffs are not rendered by default.

Loading

0 comments on commit 87427e4

Please sign in to comment.