Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for custom offline maps #318

Merged
merged 13 commits into from
May 14, 2024
4 changes: 3 additions & 1 deletion scripts/build-backend.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,9 @@ const KEEP_THESE = [
// Static folders referenced by @mapeo/core code
'node_modules/@mapeo/core/drizzle',
// zip file that is the default config
'node_modules/@mapeo/default-config/dist/mapeo-default-config.mapeoconfig'
'node_modules/@mapeo/default-config/dist/mapeo-default-config.mapeoconfig',
// Offline fallback map
'node_modules/mapeo-offline-map',
];

for (const name of KEEP_THESE) {
Expand Down
5 changes: 5 additions & 0 deletions src/backend/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ const MIGRATIONS_FOLDER_PATH = new URL(
'./node_modules/@mapeo/core/drizzle',
import.meta.url,
).pathname
const FALLBACK_MAP_PATH = new URL(
'./node_modules/mapeo-offline-map',
import.meta.url,
).pathname

const DEFAULT_CONFIG_PATH = new URL(
'./node_modules/@mapeo/default-config/dist/mapeo-default-config.mapeoconfig',
Expand Down Expand Up @@ -41,6 +45,7 @@ try {
migrationsFolderPath: MIGRATIONS_FOLDER_PATH,
sharedStoragePath: values.sharedStoragePath,
defaultConfigPath: DEFAULT_CONFIG_PATH,
fallbackMapPath: FALLBACK_MAP_PATH,
}).catch((err) => {
console.error('Server startup error:', err)
})
Expand Down
13 changes: 12 additions & 1 deletion src/backend/package-lock.json

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

3 changes: 2 additions & 1 deletion src/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"@mapeo/core": "9.0.0-alpha.9",
"@mapeo/default-config": "^4.0.0-alpha.2",
"@mapeo/ipc": "0.5.0",
"debug": "^4.3.4"
"debug": "^4.3.4",
"mapeo-offline-map": "^2.0.0"
},
"devDependencies": {
"@digidem/types": "~2.1.0",
Expand Down
31 changes: 30 additions & 1 deletion src/backend/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import { createRequire } from 'module'
const require = createRequire(import.meta.url)
/** @type {import('../types/rn-bridge.js')} */
const rnBridge = require('rn-bridge')
import { MapeoManager, FastifyController } from '@mapeo/core'
import {
MapeoManager,
FastifyController,
MapeoMapsFastifyPlugin,
MapeoStaticMapsFastifyPlugin,
MapeoOfflineFallbackMapFastifyPlugin,
} from '@mapeo/core'
import { createMapeoServer } from '@mapeo/ipc'
import Fastify from 'fastify'

Expand All @@ -16,6 +22,10 @@ import { ServerStatus } from './status.js'
const DB_DIR_NAME = 'sqlite-dbs'
const CORE_STORAGE_DIR_NAME = 'core-storage'

const MAPBOX_ACCESS_TOKEN =
'pk.eyJ1IjoiZGlnaWRlbSIsImEiOiJjbHRyaGh3cm0wN3l4Mmpsam95NDI3c2xiIn0.daq2iZFZXQ08BD0VZWAGUw'
const DEFAULT_ONLINE_MAP_STYLE_URL = `https://api.mapbox.com/styles/v1/mapbox/outdoors-v11?access_token=${MAPBOX_ACCESS_TOKEN}`

const log = debug('mapeo:app')

// Set these up as soon as possible (e.g. before the init function)
Expand Down Expand Up @@ -48,6 +58,7 @@ process.on('exit', (code) => {
* @param {string} options.migrationsFolderPath
* @param {string} options.sharedStoragePath Path to app-specific external file storage folder
* @param {string} options.defaultConfigPath
* @param {string} options.fallbackMapPath Path to app-specific external file storage folder
*
*/
export async function init({
Expand All @@ -56,20 +67,38 @@ export async function init({
migrationsFolderPath,
sharedStoragePath,
defaultConfigPath,
fallbackMapPath,
}) {
log('Starting app...')
log(`Device version is ${version}`)

const privateStorageDir = rnBridge.app.datadir()
const dbDir = join(privateStorageDir, DB_DIR_NAME)
const indexDir = join(privateStorageDir, CORE_STORAGE_DIR_NAME)
const staticStylesDir = join(sharedStoragePath, 'styles')

mkdirSync(dbDir, { recursive: true })
mkdirSync(indexDir, { recursive: true })
mkdirSync(staticStylesDir, { recursive: true })

const fastify = Fastify()
const fastifyController = new FastifyController({ fastify })

// Register maps plugins
fastify.register(MapeoStaticMapsFastifyPlugin, {
prefix: 'static',
staticRootDir: staticStylesDir,
})
fastify.register(MapeoOfflineFallbackMapFastifyPlugin, {
prefix: 'fallback',
styleJsonPath: join(fallbackMapPath, 'style.json'),
sourcesDir: join(fallbackMapPath, 'dist'),
})
fastify.register(MapeoMapsFastifyPlugin, {
prefix: 'maps',
defaultOnlineStyleUrl: DEFAULT_ONLINE_MAP_STYLE_URL,
})

Comment on lines +87 to +101
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain what this is for?

Copy link
Member Author

@achou11 achou11 May 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's to add maps functionality to the fastify instance that we use for serving things over http. the functionality is separate into 3 plugins:

  • static: provides ability to statically serve custom offline maps, similar to how Mapeo Mobile works
  • fallback: provides ability to statically serve a directory that acts as the fallback map (which are set up differently that custom offline maps). this is mostly to accommodate for the fallback map structure that's used by Mapeo Mobile.
  • maps: currently acts as the main resolver for the user-facing API e.g. resolving the appropriate style.json based on existence of static/fallback maps. in the future, this will probably hold other functionality related to features that are more similar to the Map Manager in Mapeo Mobile.

const manager = new MapeoManager({
rootKey,
dbFolder: dbDir,
Expand Down
16 changes: 16 additions & 0 deletions src/frontend/hooks/server/mapStyleUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import {useQuery} from '@tanstack/react-query';

import {useApi} from '../../contexts/ApiContext';

export const MAP_STYLE_URL_KEY = 'map_style_url';

export function useMapStyleUrl() {
const api = useApi();

return useQuery({
queryKey: [MAP_STYLE_URL_KEY],
queryFn: () => {
return api.getMapStyleJsonUrl();
},
});
}
8 changes: 5 additions & 3 deletions src/frontend/screens/MapScreen/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as React from 'react';
import Mapbox from '@rnmapbox/maps';

import config from '../../../config.json';
import {IconButton} from '../../sharedComponents/IconButton';
import {
Expand All @@ -21,6 +22,7 @@ import {GPSPermissionsModal} from './GPSPermissions/GPSPermissionsModal';
import {TrackPathLayer} from './track/TrackPathLayer';
import {UserLocation} from './UserLocation';
import {useSharedLocationContext} from '../../contexts/SharedLocationContext';
import {useMapStyleUrl} from '../../hooks/server/mapStyleUrl';

// This is the default zoom used when the map first loads, and also the zoom
// that the map will zoom to if the user clicks the "Locate" button and the
Expand All @@ -30,8 +32,6 @@ const DEFAULT_ZOOM = 12;
Mapbox.setAccessToken(config.mapboxAccessToken);
const MIN_DISPLACEMENT = 3;

export const MAP_STYLE = Mapbox.StyleURL.Outdoors;

export const MapScreen = () => {
const [zoom, setZoom] = React.useState(DEFAULT_ZOOM);
const [isFinishedLoading, setIsFinishedLoading] = React.useState(false);
Expand All @@ -45,6 +45,8 @@ export const MapScreen = () => {
const locationServicesEnabled =
!!locationProviderStatus?.locationServicesEnabled;

const styleUrlQuery = useMapStyleUrl();

const handleAddPress = () => {
newDraft();
navigate('PresetChooser');
Expand Down Expand Up @@ -75,7 +77,7 @@ export const MapScreen = () => {
attributionPosition={{right: 8, bottom: 8}}
compassEnabled={false}
scaleBarEnabled={false}
styleURL={MAP_STYLE}
styleURL={styleUrlQuery.data}
onDidFinishLoadingStyle={handleDidFinishLoadingStyle}
onMoveShouldSetResponder={() => {
if (following) setFollowing(false);
Expand Down
6 changes: 4 additions & 2 deletions src/frontend/screens/Observation/InsetMapView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {View, Text, StyleSheet, Dimensions, Image} from 'react-native';
import {BLACK, WHITE} from '../../lib/styles';
import {usePersistedSettings} from '../../hooks/persistedState/usePersistedSettings';
import {FormattedCoords} from '../../sharedComponents/FormattedData';
import {MAP_STYLE} from '../MapScreen';
import {useMapStyleUrl} from '../../hooks/server/mapStyleUrl';

const MAP_HEIGHT = 175;
const ICON_OFFSET = {x: 22, y: 21};
Expand All @@ -16,6 +16,8 @@ type MapProps = {

export const InsetMapView = React.memo<MapProps>(({lon, lat}: MapProps) => {
const format = usePersistedSettings(store => store.coordinateFormat);
const styleUrlQuery = useMapStyleUrl();

return (
<View>
<Image
Expand All @@ -37,7 +39,7 @@ export const InsetMapView = React.memo<MapProps>(({lon, lat}: MapProps) => {
rotateEnabled={false}
compassEnabled={false}
scaleBarEnabled={false}
styleURL={MAP_STYLE}>
styleURL={styleUrlQuery.data}>
<MapboxGL.Camera
centerCoordinate={[lon, lat]}
zoomLevel={12}
Expand Down
Loading