Skip to content

Commit

Permalink
feature: Update to use Maplibre with Protomaps API (#988)
Browse files Browse the repository at this point in the history
* Add MapUtils

* Load map_utils pack with defer: false

* Add new Map service for configuring default map configurations

* Use default map configuration

* feat: Use Protomaps API for Home map

* Update maplibre-gl

* feat: use Maplibre & Protomaps in Rails

* Cleanup unused method & dependencies

* Ensure Tileserver config is set to serve all fonts for appropriate fallback

* Update .env.example with new keys

* Add Protomaps API key and basemap style attributes to Theme

* Update Theme form to update protomaps attrs

* Update Map's to utilize protomaps API when provided

Also update basemap style theme when provided, default to Contrast
otherwise.

* fix: Story show map should allow panning

* Allow static map to have zoom nav controls

* Update manage community specs w/ new Map service

* Add default value for Mapbox marker colors

* Add/update Theme specs

---------

Co-authored-by: Laura Mosher <lauramosher@users.noreply.github.com>
  • Loading branch information
lauramosher and lauramosher committed Jan 15, 2024
1 parent c59ca7a commit 5abcfb6
Show file tree
Hide file tree
Showing 33 changed files with 1,215 additions and 717 deletions.
67 changes: 40 additions & 27 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,32 +1,45 @@
# This file is used to build your local dev or offline Terrastories server
# in Docker Compose.
##################################
# Configure MapLibre [Recommended]
#
# By default, Terrastories is configured to serve maps using
# Maplibre GL JS with Protomap's free API Tileserver.
#
# If you configure Mapbox, the settings in this section are ignored.

# ==> CORS
# CORS, or Cross Origin Request Sharing, allows external requests from allowed
# sites to access the resources in this application.
# The Public Community feature provides an API. For now, this API should only be available from
# allowed origins. These can be configured via this ENV variable to be configurable
# on servers without needing a code change.
CORS_ORIGINS="localhost:1080,\Ahttps:\/\/[a-z0-9]{4}-[0-9]{2}-[0-9]{3}-[0-9]{3}-[0-9]{3}.ngrok.io\z"
# ==> Protomaps Hosted API
#
# Protomaps also maintains a Tiles API - get a free API key.
# It's free for non-commercial use, or commercial use paired with a GitHub sponsorship.
#
# Sign up at https://app.protomaps.com/signup and provide your API key:
# PROTOMAPS_API_KEY=

# ==> Tileserver (hosted or local)
#
# Previously, we provided a hosted Tileserver GL server along side
# Terrastories to serve map tiles in offline mode.
#
# TILESERVER_URL=https://localhost:8080/styles/my-style/style.json

# If you are setting up your own online production instance, please see this page
# https://docs.terrastories.app/setting-up-a-terrastories-server/hosting-environments/hosting-terrastories-online
# If you have any additional questions, reach out to the Terrastories Stewards team
# for help.
##################
# Configure Mapbox
#
# For backwards compatibility, Terrastories still supports map rendering with
# Mapbox and Mapbox styles when configured.

# ==> Default Online Mapbox Configuration
# Mapbox access token and style are required to run Terrastories in an internet-
# connected environment, including local Development. These settings do not need
# to be set here; however, each community must configure them in their Theme
# settings for any mapping functionality to work.
# Set your Mapbox Access Token (DEFAULT_MAPBOX_TOKEN works, but is deprecated)
# MAPBOX_ACCESS_TOKEN=pk.ey

# Configure a Mapbox personal access token to use the mapping functionality
# of terrastories. This Access Token will be used by default across all
# onboarded communities to this instance.
# DEFAULT_MAPBOX_TOKEN=pk.set-your-key-here
# Set your Mapbox Style (DEFAULT_MAP_STYLE works, but is deprecated)
# If unset, Terrastories defaults to Mapbox's streets-v11
# MAPBOX_STYLE=mapbox://styles/mapbox/streets-v11

# Configure a custom Mapbox style. This can be any off-the-shelf style provided
# by Mapbox, or a custom style associated with your personal access token. This
# default map style will be used by default across all onboarded communities to
# this instance.
# DEFAULT_MAP_STYLE=mapbox://styles/mapbox/streets-v11
##################
# Configure CORS
#
# CORS (Cross Origin Request Sharing) allows external requests from allowed
# sites to access resources in this application.
#
# Default origins are provide for local development and ngrok.
# Additional origins can be added with comma-separation.
CORS_ORIGINS="localhost:1080,\Ahttps:\/\/[a-z0-9]{4}-[0-9]{2}-[0-9]{3}-[0-9]{3}-[0-9]{3}.ngrok.io\z"
3 changes: 0 additions & 3 deletions rails/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ gem 'pg', '>= 0.18', '< 2.0'
gem 'rgeo', '~> 2.4.0'
gem 'rgeo-geojson', '~> 2.1.1'

# Geocoder for center calculation
gem 'geocoder', '~> 1.8.1'

# Use css_bundling for stylesheets
gem 'cssbundling-rails', '~> 1.1.2'

Expand Down
2 changes: 0 additions & 2 deletions rails/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,6 @@ GEM
activerecord (>= 4.2, < 8)
flipper (~> 0.26.2)
formatador (1.1.0)
geocoder (1.8.2)
globalid (1.1.0)
activesupport (>= 5.0)
guard (2.18.0)
Expand Down Expand Up @@ -356,7 +355,6 @@ DEPENDENCIES
factory_bot_rails
flipper (~> 0.26.0)
flipper-active_record (~> 0.26.0)
geocoder (~> 1.8.1)
guard-rspec
image_processing (~> 1.12.2)
jbuilder (~> 2.5)
Expand Down
2 changes: 1 addition & 1 deletion rails/app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def current_community
end

def offline_community?
Rails.application.config.offline_mode
Map.offline?
end

def user_not_authorized
Expand Down
2 changes: 2 additions & 0 deletions rails/app/controllers/dashboard/themes_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ def theme_params
:mapbox_style_url,
:mapbox_access_token,
:mapbox_3d,
:protomaps_api_key,
:protomaps_basemap_style,
:center_lat,
:center_long,
:sw_boundary_lat,
Expand Down
6 changes: 4 additions & 2 deletions rails/app/javascript/components/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ class App extends Component {
stories: PropTypes.array,
use_local_map_server: PropTypes.bool,
mapbox_access_token: PropTypes.string,
mapbox_style: PropTypes.string,
map_style: PropTypes.string,
mapbox_3d: PropTypes.bool,
map_projection: PropTypes.string,
basemap_style: PropTypes.string,
logo_path: PropTypes.string,
user: PropTypes.object,
center_lat: PropTypes.string,
Expand Down Expand Up @@ -347,7 +348,8 @@ class App extends Component {
points={this.state.points}
mapboxAccessToken={this.props.mapbox_access_token}
useLocalMapServer={this.props.use_local_map_server}
mapStyle={this.props.mapbox_style}
basemapStyle={this.props.basemap_style}
mapStyle={this.props.map_style}
mapbox3d={this.props.mapbox_3d}
mapProjection={this.props.map_projection}
clearFilteredStories={this.resetStoriesAndMap}
Expand Down
87 changes: 34 additions & 53 deletions rails/app/javascript/components/Map.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import ReactDOM from "react-dom";
import React, { Component } from "react";
import PropTypes from "prop-types";
import Minimap from "../vendor/mapgl-minimap.js";
import Popup from "./Popup";
import { mapStyleLayers } from '../global/protomaps';

import 'mapbox-gl/dist/mapbox-gl.css';
import 'maplibre-gl/dist/maplibre-gl.css';
Expand All @@ -18,9 +20,7 @@ export default class Map extends Component {
this.state = {
activePopup: null,
mapGL: null,
isMapLibre: false,
mapModule: null,
minimapModule: null
};
}

Expand All @@ -39,34 +39,23 @@ export default class Map extends Component {
};

componentDidMount() {
if (this.state.mapModule) {
this.initializeMap(this.state.mapModule);
return;
}

if (this.props.useLocalMapServer) {
if (!this.state.mapModule) {
import('!maplibre-gl').then(module => {
this.setState({ mapModule: module.default }, () => {
this.initializeMap(this.state.mapModule, true);
});
import('!maplibre-gl').then(module => {
this.setState({ mapModule: module.default }, () => {
this.initializeMap(this.state.mapModule);
});
} else {
this.initializeMap(this.state.mapModule, true);
}
});
} else {
if (!this.state.mapModule || !this.state.minimapModule) {
Promise.all([
import('!mapbox-gl'),
import('../vendor/mapboxgl-control-minimap.js')
]).then(([mapboxGLModule, minimapModule]) => {
this.setState({
mapModule: mapboxGLModule.default,
minimapModule: minimapModule.default
}, () => {
this.Minimap = this.state.minimapModule;
this.initializeMap(this.state.mapModule, false);
});
import('!mapbox-gl').then(module => {
this.setState({ mapModule: module.default }, () => {
this.initializeMap(this.state.mapModule);
});
} else {
this.Minimap = this.state.minimapModule;
this.initializeMap(this.state.mapModule, false);
}
});
}
}

Expand Down Expand Up @@ -104,11 +93,14 @@ export default class Map extends Component {
}
}

initializeMap(mapGL, isMapLibre) {
mapGL.accessToken = this.props.useLocalMapServer ? 'pk.ey' : this.props.mapboxAccessToken;
initializeMap(mapGL) {
if (!this.props.useLocalMapServer) {
mapGL.accessToken = this.props.mapboxAccessToken;
}

this.map = new mapGL.Map({
container: this.mapContainer,
style: this.props.mapStyle,
style: mapStyleLayers(this.props.mapStyle, this.props.basemapStyle),
center: [this.props.centerLong, this.props.centerLat],
zoom: this.props.zoom,
maxBounds: this.checkBounds(), // check for bounding box presence
Expand Down Expand Up @@ -180,31 +172,21 @@ export default class Map extends Component {
this.addClusterClickHandler();
});

// Hide minimap and nav controls for offline Terrastories
if(!isMapLibre && this.Minimap) {
this.map.addControl(new this.Minimap(
{
style: this.props.mapStyle,
zoomLevels: [
[18, 14, 16],
[16, 12, 14],
[14, 10, 12],
[12, 8, 10],
[10, 6, 8],
[8, 4, 6],
[6, 2, 4],
[3, 0, 2],
[1, 0, 0]
],
lineColor: "#136a7e",
fillColor: "#d77a34",
}), "top-right");
}
// Add MiniMap
this.map.addControl(new Minimap(
mapGL,
{
center: [this.props.centerLong, this.props.centerLat],
maxBounds: this.checkBounds(),
style: mapStyleLayers(this.props.mapStyle, "light"),
lineColor: "#136a7e",
fillColor: "#d77a34",
}), "top-right");

this.map.addControl(new mapGL.NavigationControl());

// Add Maplibre logo for offline Terrastories
if(isMapLibre) {
if(this.props.useLocalMapServer) {
this.map.addControl(new mapGL.LogoControl(), 'bottom-right');
}

Expand All @@ -225,8 +207,7 @@ export default class Map extends Component {
})

this.setState({
mapGL: mapGL,
isMapLibre: isMapLibre
mapGL: mapGL
});
}

Expand Down Expand Up @@ -287,7 +268,7 @@ export default class Map extends Component {
type: "symbol",
layout: {
'text-field': '{point_count_abbreviated}',
'text-font': ['Open Sans Bold'],
'text-font': this.props.useLocalMapServer ? ['Noto Sans Medium'] : ['Open Sans Bold'],
'text-size': 16,
'text-offset': [0.2, 0.1]
},
Expand Down

0 comments on commit 5abcfb6

Please sign in to comment.