diff --git a/package.json b/package.json
index 9329620ed75..8b41dd61f99 100644
--- a/package.json
+++ b/package.json
@@ -57,6 +57,7 @@
     "@babel/runtime": "^7.12.5",
     "@sentry/browser": "^6.11.0",
     "@sentry/tracing": "^6.11.0",
+    "@types/geojson": "^7946.0.8",
     "await-lock": "^2.1.0",
     "blurhash": "^1.1.3",
     "browser-encrypt-attachment": "^0.3.0",
@@ -83,6 +84,7 @@
     "katex": "^0.12.0",
     "linkifyjs": "^2.1.9",
     "lodash": "^4.17.20",
+    "maplibre-gl": "^1.15.2",
     "matrix-js-sdk": "github:matrix-org/matrix-js-sdk#develop",
     "matrix-widget-api": "^0.1.0-beta.18",
     "minimist": "^1.2.5",
diff --git a/res/css/_common.scss b/res/css/_common.scss
index ae8a475aef2..c7e21b500c1 100644
--- a/res/css/_common.scss
+++ b/res/css/_common.scss
@@ -1,7 +1,8 @@
 /*
 Copyright 2015, 2016 OpenMarket Ltd
 Copyright 2017 Vector Creations Ltd
-Copyright 2017 New Vector Ltd
+Copyright 2017 - 2019 New Vector Ltd
+Copyright 2019 - 2021 The Matrix.org Foundation C.I.C
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -19,6 +20,7 @@ limitations under the License.
 @import "./_font-sizes.scss";
 @import "./_font-weights.scss";
 @import "./_animations.scss";
+@import url("maplibre-gl/dist/maplibre-gl.css");
 
 $hover-transition: 0.08s cubic-bezier(.46, .03, .52, .96); // quadratic
 
diff --git a/res/css/_components.scss b/res/css/_components.scss
index adfd98925a5..4bb383dee0a 100644
--- a/res/css/_components.scss
+++ b/res/css/_components.scss
@@ -172,6 +172,7 @@
 @import "./views/groups/_GroupPublicityToggle.scss";
 @import "./views/groups/_GroupRoomList.scss";
 @import "./views/groups/_GroupUserSettings.scss";
+@import "./views/location/_LocationPicker.scss";
 @import "./views/messages/_CallEvent.scss";
 @import "./views/messages/_CreateEvent.scss";
 @import "./views/messages/_DateSeparator.scss";
@@ -181,6 +182,7 @@
 @import "./views/messages/_MImageBody.scss";
 @import "./views/messages/_MImageReplyBody.scss";
 @import "./views/messages/_MJitsiWidgetEvent.scss";
+@import "./views/messages/_MLocationBody.scss";
 @import "./views/messages/_MNoticeBody.scss";
 @import "./views/messages/_MPollBody.scss";
 @import "./views/messages/_MStickerBody.scss";
diff --git a/res/css/views/location/_LocationPicker.scss b/res/css/views/location/_LocationPicker.scss
new file mode 100644
index 00000000000..b386268cec2
--- /dev/null
+++ b/res/css/views/location/_LocationPicker.scss
@@ -0,0 +1,44 @@
+/*
+Copyright 2021 The Matrix.org Foundation C.I.C
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+.mx_LocationPicker {
+    // placeholder sizing to be replaced once there's a proper design
+    width: 450px;
+    height: 500px;
+
+    border-radius: 4px;
+
+    display: flex;
+    flex-direction: column;
+}
+
+#mx_LocationPicker_map {
+    height: 324px;
+    border-radius: 8px 8px 0px 0px;
+}
+
+.mx_LocationPicker_footer {
+    margin: 24px;
+
+    .mx_Dropdown_menu {
+        max-height: 140px;
+    }
+}
+
+.mx_LocationPicker_error {
+    color: red;
+    margin: auto;
+}
diff --git a/res/css/views/messages/_MLocationBody.scss b/res/css/views/messages/_MLocationBody.scss
new file mode 100644
index 00000000000..5bb90fbf7b0
--- /dev/null
+++ b/res/css/views/messages/_MLocationBody.scss
@@ -0,0 +1,22 @@
+/*
+Copyright 2021 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+.mx_MLocationBody_map {
+    width: 450px;
+    height: 300px;
+
+    border-radius: $timeline-image-border-radius;
+}
diff --git a/res/css/views/rooms/_MessageComposer.scss b/res/css/views/rooms/_MessageComposer.scss
index 2a49840f17e..454d604572d 100644
--- a/res/css/views/rooms/_MessageComposer.scss
+++ b/res/css/views/rooms/_MessageComposer.scss
@@ -256,6 +256,10 @@ limitations under the License.
     mask-image: url('$(res)/img/element-icons/room/composer/emoji.svg');
 }
 
+.mx_MessageComposer_location::before {
+    mask-image: url('$(res)/img/element-icons/room/composer/location.svg');
+}
+
 .mx_MessageComposer_stickers::before {
     mask-image: url('$(res)/img/element-icons/room/composer/sticker.svg');
 }
diff --git a/res/img/element-icons/room/composer/location.svg b/res/img/element-icons/room/composer/location.svg
new file mode 100644
index 00000000000..71186454433
--- /dev/null
+++ b/res/img/element-icons/room/composer/location.svg
@@ -0,0 +1,3 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M12 2C8.13 2 5 5.13 5 9C5 13.17 9.42 18.92 11.24 21.11C11.64 21.59 12.37 21.59 12.77 21.11C14.58 18.92 19 13.17 19 9C19 5.13 15.87 2 12 2ZM12 11.5C10.62 11.5 9.5 10.38 9.5 9C9.5 7.62 10.62 6.5 12 6.5C13.38 6.5 14.5 7.62 14.5 9C14.5 10.38 13.38 11.5 12 11.5Z" fill="#0DBD8B"/>
+</svg>
diff --git a/src/components/views/location/LocationPicker.tsx b/src/components/views/location/LocationPicker.tsx
new file mode 100644
index 00000000000..8d4efdfa709
--- /dev/null
+++ b/src/components/views/location/LocationPicker.tsx
@@ -0,0 +1,200 @@
+/*
+Copyright 2021 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+import maplibregl from 'maplibre-gl';
+
+import SdkConfig from '../../../SdkConfig';
+import Field from "../elements/Field";
+import DialogButtons from "../elements/DialogButtons";
+import Dropdown from "../elements/Dropdown";
+import LocationShareType from "./LocationShareType";
+
+import { _t } from '../../../languageHandler';
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+import { logger } from "matrix-js-sdk/src/logger";
+
+interface IDropdownProps {
+    value: LocationShareType;
+    label: string;
+    width?: number;
+    onChange(type: LocationShareType): void;
+}
+
+const LocationShareTypeDropdown = ({
+    value,
+    label,
+    width,
+    onChange,
+}: IDropdownProps) => {
+    const options = [
+        // <div key={LocationShareType.Custom}>{ _t("Share custom location") }</div>,
+        <div key={LocationShareType.OnceOff}>{ _t("Share my current location as a once off") }</div>,
+        // <div key={LocationShareType.OneMin}>{ _t("Share my current location for one minute") }</div>,
+        // <div key={LocationShareType.FiveMins}>{ _t("Share my current location for five minutes") }</div>,
+        // <div key={LocationShareType.ThirtyMins}>{ _t("Share my current location for thirty minutes") }</div>,
+        // <div key={LocationShareType.OneHour}>{ _t("Share my current location for one hour") }</div>,
+        // <div key={LocationShareType.ThreeHours}>{ _t("Share my current location for three hours") }</div>,
+        // <div key={LocationShareType.SixHours}>{ _t("Share my current location for six hours") }</div>,
+        // <div key={LocationShareType.OneDay}>{ _t("Share my current location for one day") }</div>,
+        // <div key={LocationShareType.Forever}>{ _t("Share my current location until I disable it") }</div>,
+    ];
+
+    return <Dropdown
+        id="mx_LocationShareTypeDropdown"
+        className="mx_LocationShareTypeDropdown"
+        onOptionChange={(key: string)=>{ onChange(LocationShareType[LocationShareType[parseInt(key)]]); }}
+        menuWidth={width}
+        label={label}
+        value={value.toString()}
+    >
+        { options }
+    </Dropdown>;
+};
+
+interface IProps {
+    onChoose(uri: string, ts: number, type: LocationShareType, description: string): boolean;
+    onFinished();
+}
+
+interface IState {
+    description: string;
+    type: LocationShareType;
+    position?: GeolocationPosition;
+    manual: boolean;
+    error: Error;
+}
+
+@replaceableComponent("views.location.LocationPicker")
+class LocationPicker extends React.Component<IProps, IState> {
+    private map: maplibregl.Map;
+    private geolocate: maplibregl.GeolocateControl;
+
+    constructor(props) {
+        super(props);
+
+        this.state = {
+            description: _t("My location"),
+            type: LocationShareType.OnceOff,
+            position: undefined,
+            manual: false,
+            error: undefined,
+        };
+    }
+
+    componentDidMount() {
+        const config = SdkConfig.get();
+        this.map = new maplibregl.Map({
+            container: 'mx_LocationPicker_map',
+            style: config.map_style_url,
+            center: [0, 0],
+            zoom: 1,
+        });
+
+        // Add geolocate control to the map.
+        this.geolocate = new maplibregl.GeolocateControl({
+            positionOptions: {
+                enableHighAccuracy: true,
+            },
+            trackUserLocation: true,
+        });
+        this.map.addControl(this.geolocate);
+
+        this.map.on('error', (e)=>{
+            logger.error("Failed to load map: check map_style_url in config.json has a valid URL and API key", e.error);
+            this.setState({ error: e.error });
+        });
+
+        this.map.on('load', ()=>{
+            this.geolocate.trigger();
+        });
+
+        this.geolocate.on('geolocate', this.onGeolocate);
+    }
+
+    componentWillUnmount() {
+        this.geolocate.off('geolocate', this.onGeolocate);
+    }
+
+    private onGeolocate = (position) => {
+        this.setState({ position });
+    };
+
+    private onDescriptionChange = (ev: React.ChangeEvent<HTMLInputElement>) => {
+        this.setState({ description: ev.target.value });
+    };
+
+    private getGeoUri = (position) => {
+        return (`geo:${ position.coords.latitude },` +
+                position.coords.longitude +
+                ( position.coords.altitude != null ?
+                    `,${ position.coords.altitude }` : '' ) +
+                `;u=${ position.coords.accuracy }`);
+    };
+
+    private onOk = () => {
+        this.props.onChoose(
+            this.state.position ? this.getGeoUri(this.state.position) : undefined,
+            this.state.position ? this.state.position.timestamp : undefined,
+            this.state.type,
+            this.state.description,
+        );
+        this.props.onFinished();
+    };
+
+    private onTypeChange= (type: LocationShareType) => {
+        this.setState({ type });
+    };
+
+    render() {
+        const error = this.state.error ?
+            <div className="mx_LocationPicker_error">
+                { _t("Failed to load map") }
+            </div> : null;
+
+        return (
+            <div className="mx_LocationPicker">
+                <div id="mx_LocationPicker_map" />
+                { error }
+                <div className="mx_LocationPicker_footer">
+                    <form onSubmit={this.onOk}>
+                        <LocationShareTypeDropdown
+                            value={this.state.type}
+                            label={_t("Type of location share")}
+                            onChange={this.onTypeChange}
+                            width={400}
+                        />
+
+                        <Field
+                            label={_t('Description')}
+                            onChange={this.onDescriptionChange}
+                            value={this.state.description}
+                            width={400}
+                            className="mx_LocationPicker_description"
+                        />
+
+                        <DialogButtons primaryButton={_t('Share')}
+                            onPrimaryButtonClick={this.onOk}
+                            onCancel={this.props.onFinished}
+                            disabled={!this.state.position} />
+                    </form>
+                </div>
+            </div>
+        );
+    }
+}
+
+export default LocationPicker;
diff --git a/src/components/views/location/LocationShareType.tsx b/src/components/views/location/LocationShareType.tsx
new file mode 100644
index 00000000000..957860514ef
--- /dev/null
+++ b/src/components/views/location/LocationShareType.tsx
@@ -0,0 +1,30 @@
+/*
+Copyright 2021 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+enum LocationShareType {
+    Custom = -1,
+    OnceOff = 0,
+    OneMine = 60,
+    FiveMins = 5 * 60,
+    ThirtyMins = 30 * 60,
+    OneHour = 60 * 60,
+    ThreeHours = 3 * 60 * 60,
+    SixHours = 6 * 60 * 60,
+    OneDay = 24 * 60 * 60,
+    Forever = Number.MAX_SAFE_INTEGER,
+}
+
+export default LocationShareType;
diff --git a/src/components/views/messages/MLocationBody.tsx b/src/components/views/messages/MLocationBody.tsx
new file mode 100644
index 00000000000..8e096fa3820
--- /dev/null
+++ b/src/components/views/messages/MLocationBody.tsx
@@ -0,0 +1,112 @@
+/*
+Copyright 2021 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+import maplibregl from 'maplibre-gl';
+import SdkConfig from '../../../SdkConfig';
+import { replaceableComponent } from "../../../utils/replaceableComponent";
+import { IBodyProps } from "./IBodyProps";
+import { logger } from "matrix-js-sdk/src/logger";
+import { _t } from '../../../languageHandler';
+
+interface IState {
+    error: Error;
+}
+
+@replaceableComponent("views.messages.MLocationBody")
+export default class MLocationBody extends React.Component<IBodyProps, IState> {
+    private map: maplibregl.Map;
+    private coords: GeolocationCoordinates;
+    private description: string;
+
+    constructor(props: IBodyProps) {
+        super(props);
+
+        // unfortunately we're stuck supporting legacy `content.geo_uri`
+        // events until the end of days, or until we figure out mutable
+        // events - so folks can read their old chat history correctly.
+        // https://github.com/matrix-org/matrix-doc/issues/3516
+        const content = this.props.mxEvent.getContent();
+        const uri = content['org.matrix.msc3488.location'] ?
+            content['org.matrix.msc3488.location'].uri :
+            content['geo_uri'];
+
+        this.coords = this.parseGeoUri(uri);
+        this.state = {
+            error: undefined,
+        };
+
+        this.description =
+            content['org.matrix.msc3488.location']?.description ?? content['body'];
+    }
+
+    private parseGeoUri = (uri: string): GeolocationCoordinates => {
+        const m = uri.match(/^\s*geo:(.*?)\s*$/);
+        if (!m) return;
+        const parts = m[1].split(';');
+        const coords = parts[0].split(',');
+        let uncertainty: number;
+        for (const param of parts.slice(1)) {
+            const m = param.match(/u=(.*)/);
+            if (m) uncertainty = parseFloat(m[1]);
+        }
+        return {
+            latitude: parseFloat(coords[0]),
+            longitude: parseFloat(coords[1]),
+            altitude: parseFloat(coords[2]),
+            accuracy: uncertainty,
+            altitudeAccuracy: undefined,
+            heading: undefined,
+            speed: undefined,
+        };
+    };
+
+    componentDidMount() {
+        const config = SdkConfig.get();
+        this.map = new maplibregl.Map({
+            container: this.getBodyId(),
+            style: config.map_style_url,
+            center: [this.coords.longitude, this.coords.latitude],
+            zoom: 13,
+        });
+
+        new maplibregl.Marker()
+            .setLngLat([this.coords.longitude, this.coords.latitude])
+            .addTo(this.map);
+
+        this.map.on('error', (e)=>{
+            logger.error("Failed to load map: check map_style_url in config.json has a valid URL and API key", e.error);
+            this.setState({ error: e.error });
+        });
+    }
+
+    private getBodyId = () => {
+        return `mx_MLocationBody_${this.props.mxEvent.getId()}`;
+    };
+
+    render() {
+        const error = this.state.error ?
+            <div className="mx_EventTile_tileError mx_EventTile_body">
+                { _t("Failed to load map") }
+            </div> : null;
+
+        return <div className="mx_MLocationBody">
+            <div id={this.getBodyId()} className="mx_MLocationBody_map" />
+            { error }
+            <span className="mx_EventTile_body">{ this.description }</span>
+        </div>;
+    }
+}
diff --git a/src/components/views/messages/MessageEvent.tsx b/src/components/views/messages/MessageEvent.tsx
index 6cffd791498..2896eafe8e9 100644
--- a/src/components/views/messages/MessageEvent.tsx
+++ b/src/components/views/messages/MessageEvent.tsx
@@ -125,6 +125,14 @@ export default class MessageEvent extends React.Component<IProps> implements IMe
                     BodyType = sdk.getComponent('messages.MPollBody');
                 }
             }
+
+            if ((type && type === "org.matrix.msc3488.location") ||
+                (type && type === EventType.RoomMessage && msgtype && msgtype === MsgType.Location)) {
+                // TODO: tidy this up once location sharing is out of labs
+                if (SettingsStore.getValue("feature_location_share")) {
+                    BodyType = sdk.getComponent('messages.MLocationBody');
+                }
+            }
         }
 
         if (SettingsStore.getValue("feature_mjolnir")) {
diff --git a/src/components/views/rooms/MessageComposer.tsx b/src/components/views/rooms/MessageComposer.tsx
index aedad1848c7..d64bef9d2d8 100644
--- a/src/components/views/rooms/MessageComposer.tsx
+++ b/src/components/views/rooms/MessageComposer.tsx
@@ -48,6 +48,7 @@ import { ComposerInsertPayload } from "../../../dispatcher/payloads/ComposerInse
 import { Action } from "../../../dispatcher/actions";
 import EditorModel from "../../../editor/model";
 import EmojiPicker from '../emojipicker/EmojiPicker';
+import LocationPicker from '../location/LocationPicker';
 import UIStore, { UI_EVENTS } from '../../../stores/UIStore';
 import Modal from "../../../Modal";
 import { RelationType } from 'matrix-js-sdk/src/@types/event';
@@ -55,6 +56,9 @@ import RoomContext from '../../../contexts/RoomContext';
 import { POLL_START_EVENT_TYPE } from "../../../polls/consts";
 import ErrorDialog from "../dialogs/ErrorDialog";
 import PollCreateDialog from "../elements/PollCreateDialog";
+import { MsgType } from "matrix-js-sdk/src/@types/event";
+import { logger } from "matrix-js-sdk/src/logger";
+import LocationShareType from "../location/LocationShareType";
 import { SettingUpdatedPayload } from "../../../dispatcher/payloads/SettingUpdatedPayload";
 
 let instanceCount = 0;
@@ -77,7 +81,7 @@ function SendButton(props: ISendButtonProps) {
 
 interface IEmojiButtonProps {
     addEmoji: (unicode: string) => boolean;
-    menuPosition: any; // TODO: Types
+    menuPosition: AboveLeftOf;
     narrowMode: boolean;
 }
 
@@ -114,6 +118,46 @@ const EmojiButton: React.FC<IEmojiButtonProps> = ({ addEmoji, menuPosition, narr
     </React.Fragment>;
 };
 
+interface ILocationButtonProps {
+    room: Room;
+    shareLocation: (uri: string, ts: number, type: LocationShareType, description: string) => boolean;
+    menuPosition: AboveLeftOf;
+    narrowMode: boolean;
+}
+
+const LocationButton: React.FC<ILocationButtonProps> = ({ shareLocation, menuPosition, narrowMode }) => {
+    const [menuDisplayed, button, openMenu, closeMenu] = useContextMenu();
+
+    let contextMenu;
+    if (menuDisplayed) {
+        const position = menuPosition ?? aboveLeftOf(button.current.getBoundingClientRect());
+        contextMenu = <ContextMenu {...position} onFinished={closeMenu} managed={false}>
+            <LocationPicker onChoose={shareLocation} onFinished={closeMenu} />
+        </ContextMenu>;
+    }
+
+    const className = classNames(
+        "mx_MessageComposer_button",
+        "mx_MessageComposer_location",
+        {
+            "mx_MessageComposer_button_highlight": menuDisplayed,
+        },
+    );
+
+    // TODO: replace ContextMenuTooltipButton with a unified representation of
+    // the header buttons and the right panel buttons
+    return <React.Fragment>
+        <AccessibleTooltipButton
+            className={className}
+            onClick={openMenu}
+            title={!narrowMode && _t('Share location')}
+            label={narrowMode ? _t('Share location') : null}
+        />
+
+        { contextMenu }
+    </React.Fragment>;
+};
+
 interface IUploadButtonProps {
     roomId: string;
     relation?: IEventRelation | null;
@@ -447,6 +491,25 @@ export default class MessageComposer extends React.Component<IProps, IState> {
         return true;
     };
 
+    private shareLocation = (uri: string, ts: number, type: LocationShareType, description: string): boolean => {
+        if (!uri) return false;
+        try {
+            const text = `${description ? description : 'Location'} at ${uri} as of ${new Date(ts).toISOString()}`;
+            // noinspection ES6MissingAwait - we don't care if it fails, it'll get queued.
+            MatrixClientPeg.get().sendMessage(this.props.room.roomId, {
+                "body": text,
+                "msgtype": MsgType.Location,
+                "geo_uri": uri,
+                "org.matrix.msc3488.location": { uri, description },
+                "org.matrix.msc3488.ts": ts,
+                // TODO: MSC1767 fallbacks for text & thumbnail
+            });
+        } catch (e) {
+            logger.error("Error sending location:", e);
+        }
+        return true;
+    };
+
     private sendMessage = async () => {
         if (this.state.haveRecording && this.voiceRecordingButton.current) {
             // There shouldn't be any text message to send when a voice recording is active, so
@@ -514,6 +577,17 @@ export default class MessageComposer extends React.Component<IProps, IState> {
                     relation={this.props.relation}
                 />,
             );
+            if (SettingsStore.getValue("feature_location_share")) {
+                buttons.push(
+                    <LocationButton
+                        key="location"
+                        room={this.props.room}
+                        shareLocation={this.shareLocation}
+                        menuPosition={menuPosition}
+                        narrowMode={this.state.narrowMode}
+                    />,
+                );
+            }
             buttons.push(
                 <EmojiButton key="emoji_button" addEmoji={this.addEmoji} menuPosition={menuPosition} narrowMode={this.state.narrowMode} />,
             );
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 02123310158..cc24b68419c 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -847,6 +847,7 @@
     "Offline encrypted messaging using dehydrated devices": "Offline encrypted messaging using dehydrated devices",
     "Send pseudonymous analytics data": "Send pseudonymous analytics data",
     "Polls (under active development)": "Polls (under active development)",
+    "Location sharing (under active development)": "Location sharing (under active development)",
     "Show info about bridges in room settings": "Show info about bridges in room settings",
     "New layout switcher (with message bubbles)": "New layout switcher (with message bubbles)",
     "Meta Spaces": "Meta Spaces",
@@ -1642,6 +1643,7 @@
     "Send message": "Send message",
     "Emoji picker": "Emoji picker",
     "Add emoji": "Add emoji",
+    "Share location": "Share location",
     "Upload file": "Upload file",
     "You do not have permission to start polls in this room.": "You do not have permission to start polls in this room.",
     "Create poll": "Create poll",
@@ -2063,6 +2065,7 @@
     "Declining …": "Declining …",
     "%(name)s wants to verify": "%(name)s wants to verify",
     "You sent a verification request": "You sent a verification request",
+    "Failed to load map": "Failed to load map",
     "Vote not registered": "Vote not registered",
     "Sorry, your vote was not registered. Please try again.": "Sorry, your vote was not registered. Please try again.",
     "No votes cast": "No votes cast",
@@ -2093,6 +2096,9 @@
     "edited": "edited",
     "Submit logs": "Submit logs",
     "Can't load this message": "Can't load this message",
+    "Share my current location as a once off": "Share my current location as a once off",
+    "My location": "My location",
+    "Type of location share": "Type of location share",
     "Failed to load group members": "Failed to load group members",
     "Filter community members": "Filter community members",
     "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Are you sure you want to remove '%(roomName)s' from %(groupId)s?",
diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx
index 346af1c766b..508a6078650 100644
--- a/src/settings/Settings.tsx
+++ b/src/settings/Settings.tsx
@@ -316,6 +316,13 @@ export const SETTINGS: {[setting: string]: ISetting} = {
         displayName: _td("Polls (under active development)"),
         default: false,
     },
+    "feature_location_share": {
+        isFeature: true,
+        labsGroup: LabGroup.Messaging,
+        supportedLevels: LEVELS_FEATURE,
+        displayName: _td("Location sharing (under active development)"),
+        default: false,
+    },
     "doNotDisturb": {
         supportedLevels: [SettingLevel.DEVICE],
         default: false,
diff --git a/src/settings/controllers/SettingController.ts b/src/settings/controllers/SettingController.ts
index ba78597da7b..292b2d63e59 100644
--- a/src/settings/controllers/SettingController.ts
+++ b/src/settings/controllers/SettingController.ts
@@ -54,6 +54,8 @@ export default abstract class SettingController {
      */
     public onChange(level: SettingLevel, roomId: string, newValue: any) {
         // do nothing by default
+
+        // FIXME: force a fresh on the RoomView for the roomId in question
     }
 
     /**
diff --git a/test/setupTests.js b/test/setupTests.js
index e719f715c9e..0b92c0c8546 100644
--- a/test/setupTests.js
+++ b/test/setupTests.js
@@ -15,3 +15,5 @@ global.TextDecoder = TextDecoder;
 
 configure({ adapter: new Adapter() });
 
+// maplibre requires a createObjectURL mock
+global.URL.createObjectURL = jest.fn();
diff --git a/yarn.lock b/yarn.lock
index 83b24389f78..0f94f38e558 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1306,6 +1306,56 @@
     "@types/yargs" "^15.0.0"
     chalk "^4.0.0"
 
+"@mapbox/geojson-rewind@^0.5.0":
+  version "0.5.1"
+  resolved "https://registry.yarnpkg.com/@mapbox/geojson-rewind/-/geojson-rewind-0.5.1.tgz#adbe16dc683eb40e90934c51a5e28c7bbf44f4e1"
+  integrity sha512-eL7fMmfTBKjrb+VFHXCGv9Ot0zc3C0U+CwXo1IrP+EPwDczLoXv34Tgq3y+2mPSFNVUXgU42ILWJTC7145KPTA==
+  dependencies:
+    get-stream "^6.0.1"
+    minimist "^1.2.5"
+
+"@mapbox/geojson-types@^1.0.2":
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/@mapbox/geojson-types/-/geojson-types-1.0.2.tgz#9aecf642cb00eab1080a57c4f949a65b4a5846d6"
+  integrity sha512-e9EBqHHv3EORHrSfbR9DqecPNn+AmuAoQxV6aL8Xu30bJMJR1o8PZLZzpk1Wq7/NfCbuhmakHTPYRhoqLsXRnw==
+
+"@mapbox/jsonlint-lines-primitives@^2.0.2":
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/@mapbox/jsonlint-lines-primitives/-/jsonlint-lines-primitives-2.0.2.tgz#ce56e539f83552b58d10d672ea4d6fc9adc7b234"
+  integrity sha1-zlblOfg1UrWNENZy6k1vya3HsjQ=
+
+"@mapbox/mapbox-gl-supported@^1.5.0":
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/@mapbox/mapbox-gl-supported/-/mapbox-gl-supported-1.5.0.tgz#f60b6a55a5d8e5ee908347d2ce4250b15103dc8e"
+  integrity sha512-/PT1P6DNf7vjEEiPkVIRJkvibbqWtqnyGaBz3nfRdcxclNSnSdaLU5tfAgcD7I8Yt5i+L19s406YLl1koLnLbg==
+
+"@mapbox/point-geometry@0.1.0", "@mapbox/point-geometry@^0.1.0", "@mapbox/point-geometry@~0.1.0":
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz#8a83f9335c7860effa2eeeca254332aa0aeed8f2"
+  integrity sha1-ioP5M1x4YO/6Lu7KJUMyqgru2PI=
+
+"@mapbox/tiny-sdf@^1.1.1":
+  version "1.2.5"
+  resolved "https://registry.yarnpkg.com/@mapbox/tiny-sdf/-/tiny-sdf-1.2.5.tgz#424c620a96442b20402552be70a7f62a8407cc59"
+  integrity sha512-cD8A/zJlm6fdJOk6DqPUV8mcpyJkRz2x2R+/fYcWDYG3oWbG7/L7Yl/WqQ1VZCjnL9OTIMAn6c+BC5Eru4sQEw==
+
+"@mapbox/unitbezier@^0.0.0":
+  version "0.0.0"
+  resolved "https://registry.yarnpkg.com/@mapbox/unitbezier/-/unitbezier-0.0.0.tgz#15651bd553a67b8581fb398810c98ad86a34524e"
+  integrity sha1-FWUb1VOme4WB+zmIEMmK2Go0Uk4=
+
+"@mapbox/vector-tile@^1.3.1":
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz#d3a74c90402d06e89ec66de49ec817ff53409666"
+  integrity sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==
+  dependencies:
+    "@mapbox/point-geometry" "~0.1.0"
+
+"@mapbox/whoots-js@^3.1.0":
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz#497c67a1cef50d1a2459ba60f315e448d2ad87fe"
+  integrity sha512-Es6WcD0nO5l+2BOQS4uLfNPYQaNDfbot3X1XUoloz+x0mPDS3eeORZJl06HXjwBG1fOGwCRnzK88LMdxKRrd6Q==
+
 "@matrix-org/olm@https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz":
   version "3.2.3"
   resolved "https://gitlab.matrix.org/api/v4/projects/27/packages/npm/@matrix-org/olm/-/@matrix-org/olm-3.2.3.tgz#cc332fdd25c08ef0e40f4d33fc3f822a0f98b6f4"
@@ -1672,6 +1722,11 @@
     "@types/fbemitter" "*"
     "@types/react" "*"
 
+"@types/geojson@^7946.0.8":
+  version "7946.0.8"
+  resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.8.tgz#30744afdb385e2945e22f3b033f897f76b1f12ca"
+  integrity sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==
+
 "@types/graceful-fs@^4.1.2":
   version "4.1.5"
   resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15"
@@ -2996,6 +3051,11 @@ css-what@^5.0.0, css-what@^5.0.1:
   resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe"
   integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw==
 
+csscolorparser@~1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/csscolorparser/-/csscolorparser-1.0.3.tgz#b34f391eea4da8f3e98231e2ccd8df9c041f171b"
+  integrity sha1-s085HupNqPPpgjHizNjfnAQfFxs=
+
 cssesc@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
@@ -3277,6 +3337,11 @@ domutils@^2.5.2, domutils@^2.6.0, domutils@^2.7.0:
     domelementtype "^2.2.0"
     domhandler "^4.2.0"
 
+earcut@^2.2.2:
+  version "2.2.3"
+  resolved "https://registry.yarnpkg.com/earcut/-/earcut-2.2.3.tgz#d44ced2ff5a18859568e327dd9c7d46b16f55cf4"
+  integrity sha512-iRDI1QeCQIhMCZk48DRDMVgQSSBDmbzzNhnxIo+pwx3swkfjMh6vh0nWLq1NdvGHLKH6wIrAM3vQWeTj6qeoug==
+
 ecc-jsbn@~0.1.1:
   version "0.1.2"
   resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
@@ -4112,6 +4177,11 @@ gensync@^1.0.0-beta.2:
   resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
   integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
 
+geojson-vt@^3.2.1:
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/geojson-vt/-/geojson-vt-3.2.1.tgz#f8adb614d2c1d3f6ee7c4265cad4bbf3ad60c8b7"
+  integrity sha512-EvGQQi/zPrDA6zr6BnJD/YhwAkBP8nnJ9emh3EnHQKVMfg/MRVtPbMYdgVy/IaEmn4UfagD2a6fafPDL5hbtwg==
+
 get-caller-file@^2.0.1, get-caller-file@^2.0.5:
   version "2.0.5"
   resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
@@ -4150,6 +4220,11 @@ get-stream@^5.0.0:
   dependencies:
     pump "^3.0.0"
 
+get-stream@^6.0.1:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
+  integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
+
 get-symbol-description@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6"
@@ -4175,6 +4250,11 @@ gfm.css@^1.1.2:
   resolved "https://registry.yarnpkg.com/gfm.css/-/gfm.css-1.1.2.tgz#94acfa600672663b9dd0fd4b6ee5d11c8dbc161e"
   integrity sha512-KhK3rqxMj+UTLRxWnfUA5n8XZYMWfHrrcCxtWResYR2B3hWIqBM6v9FPGZSlVuX+ScLewizOvNkjYXuPs95ThQ==
 
+gl-matrix@^3.2.1:
+  version "3.4.3"
+  resolved "https://registry.yarnpkg.com/gl-matrix/-/gl-matrix-3.4.3.tgz#fc1191e8320009fd4d20e9339595c6041ddc22c9"
+  integrity sha512-wcCp8vu8FT22BnvKVPjXa/ICBWRq/zjFfdofZy1WSpQZpphblv12/bOQLBC1rMM7SGOFS9ltVmKOHil5+Ml7gA==
+
 glob-parent@^5.0.0, glob-parent@^5.1.2, glob-parent@~5.1.2:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
@@ -4256,6 +4336,11 @@ graceful-fs@^4.2.4:
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
   integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
 
+grid-index@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/grid-index/-/grid-index-1.1.0.tgz#97f8221edec1026c8377b86446a7c71e79522ea7"
+  integrity sha512-HZRwumpOGUrHyxO5bqKZL0B0GlUpwtCAzZ42sgxUPniu33R1LSFH5yrIcBCHjkctCAh3mtWKcKd9J4vDDdeVHA==
+
 growly@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
@@ -4465,7 +4550,7 @@ iconv-lite@^0.6.2:
   dependencies:
     safer-buffer ">= 2.1.2 < 3.0.0"
 
-ieee754@^1.1.13:
+ieee754@^1.1.12, ieee754@^1.1.13:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
   integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
@@ -5638,6 +5723,11 @@ katex@^0.12.0:
   dependencies:
     commander "^2.19.0"
 
+kdbush@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-3.0.0.tgz#f8484794d47004cc2d85ed3a79353dbe0abc2bf0"
+  integrity sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==
+
 kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
   version "3.2.2"
   resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
@@ -5864,6 +5954,35 @@ map-visit@^1.0.0:
   dependencies:
     object-visit "^1.0.0"
 
+maplibre-gl@^1.15.2:
+  version "1.15.2"
+  resolved "https://registry.yarnpkg.com/maplibre-gl/-/maplibre-gl-1.15.2.tgz#7fb47868b62455af916c090903f2154394450f9c"
+  integrity sha512-uPeV530apb4JfX3cRFfE+awFnbcJTOnCv2QvY4mw4huiInbybElWYkNzTs324YLSADq0f4bidRoYcR81ho3aLA==
+  dependencies:
+    "@mapbox/geojson-rewind" "^0.5.0"
+    "@mapbox/geojson-types" "^1.0.2"
+    "@mapbox/jsonlint-lines-primitives" "^2.0.2"
+    "@mapbox/mapbox-gl-supported" "^1.5.0"
+    "@mapbox/point-geometry" "^0.1.0"
+    "@mapbox/tiny-sdf" "^1.1.1"
+    "@mapbox/unitbezier" "^0.0.0"
+    "@mapbox/vector-tile" "^1.3.1"
+    "@mapbox/whoots-js" "^3.1.0"
+    csscolorparser "~1.0.3"
+    earcut "^2.2.2"
+    geojson-vt "^3.2.1"
+    gl-matrix "^3.2.1"
+    grid-index "^1.1.0"
+    minimist "^1.2.5"
+    murmurhash-js "^1.0.0"
+    pbf "^3.2.1"
+    potpack "^1.0.1"
+    quickselect "^2.0.0"
+    rw "^1.3.3"
+    supercluster "^7.1.0"
+    tinyqueue "^2.0.3"
+    vt-pbf "^3.1.1"
+
 mathml-tag-names@^2.1.3:
   version "2.1.3"
   resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz#4ddadd67308e780cf16a47685878ee27b736a0a3"
@@ -6101,6 +6220,11 @@ ms@2.1.2:
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
   integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
 
+murmurhash-js@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/murmurhash-js/-/murmurhash-js-1.0.0.tgz#b06278e21fc6c37fa5313732b0412bcb6ae15f51"
+  integrity sha1-sGJ44h/Gw3+lMTcysEEry2rhX1E=
+
 nanoid@^3.1.28:
   version "3.1.30"
   resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.30.tgz#63f93cc548d2a113dc5dfbc63bfa09e2b9b64362"
@@ -6570,6 +6694,14 @@ path-type@^4.0.0:
   resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
   integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
 
+pbf@^3.2.1:
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/pbf/-/pbf-3.2.1.tgz#b4c1b9e72af966cd82c6531691115cc0409ffe2a"
+  integrity sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==
+  dependencies:
+    ieee754 "^1.1.12"
+    resolve-protobuf-schema "^2.1.0"
+
 performance-now@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
@@ -6731,6 +6863,11 @@ posthog-js@1.12.2:
   dependencies:
     fflate "^0.4.1"
 
+potpack@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/potpack/-/potpack-1.0.2.tgz#23b99e64eb74f5741ffe7656b5b5c4ddce8dfc14"
+  integrity sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==
+
 prelude-ls@^1.2.1:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@@ -6790,6 +6927,11 @@ prop-types@^15.6.2, prop-types@^15.7.0, prop-types@^15.7.2:
     object-assign "^4.1.1"
     react-is "^16.8.1"
 
+protocol-buffers-schema@^3.3.1:
+  version "3.6.0"
+  resolved "https://registry.yarnpkg.com/protocol-buffers-schema/-/protocol-buffers-schema-3.6.0.tgz#77bc75a48b2ff142c1ad5b5b90c94cd0fa2efd03"
+  integrity sha512-TdDRD+/QNdrCGCE7v8340QyuXd4kIWIgapsE2+n/SaGiSSbomYl4TjHlvIoCWRpE7wFt02EpB35VVA2ImcBVqw==
+
 psl@^1.1.28, psl@^1.1.33:
   version "1.8.0"
   resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
@@ -6865,6 +7007,11 @@ quick-lru@^4.0.1:
   resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f"
   integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==
 
+quickselect@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/quickselect/-/quickselect-2.0.0.tgz#f19680a486a5eefb581303e023e98faaf25dd018"
+  integrity sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw==
+
 raf-schd@^4.0.2:
   version "4.0.3"
   resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
@@ -7262,6 +7409,13 @@ resolve-from@^5.0.0:
   resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
   integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
 
+resolve-protobuf-schema@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz#9ca9a9e69cf192bbdaf1006ec1973948aa4a3758"
+  integrity sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==
+  dependencies:
+    protocol-buffers-schema "^3.3.1"
+
 resolve-url@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
@@ -7335,6 +7489,11 @@ run-parallel@^1.1.9:
   dependencies:
     queue-microtask "^1.2.2"
 
+rw@^1.3.3:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4"
+  integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=
+
 rxjs@^6.5.2:
   version "6.6.7"
   resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9"
@@ -7915,6 +8074,13 @@ sugarss@^2.0.0:
   dependencies:
     postcss "^7.0.2"
 
+supercluster@^7.1.0:
+  version "7.1.4"
+  resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-7.1.4.tgz#6762aabfd985d3390b49f13b815567d5116a828a"
+  integrity sha512-GhKkRM1jMR6WUwGPw05fs66pOFWhf59lXq+Q3J3SxPvhNcmgOtLRV6aVQPMRsmXdpaeFJGivt+t7QXUPL3ff4g==
+  dependencies:
+    kdbush "^3.0.0"
+
 supports-color@^5.3.0:
   version "5.5.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@@ -8011,6 +8177,11 @@ tiny-invariant@^1.0.6:
   resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875"
   integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw==
 
+tinyqueue@^2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-2.0.3.tgz#64d8492ebf39e7801d7bd34062e29b45b2035f08"
+  integrity sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==
+
 tmatch@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/tmatch/-/tmatch-2.0.1.tgz#0c56246f33f30da1b8d3d72895abaf16660f38cf"
@@ -8406,6 +8577,15 @@ vfile@^4.0.0:
     unist-util-stringify-position "^2.0.0"
     vfile-message "^2.0.0"
 
+vt-pbf@^3.1.1:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/vt-pbf/-/vt-pbf-3.1.3.tgz#68fd150756465e2edae1cc5c048e063916dcfaac"
+  integrity sha512-2LzDFzt0mZKZ9IpVF2r69G9bXaP2Q2sArJCmcCgvfTdCCZzSyz4aCLoQyUilu37Ll56tCblIZrXFIjNUpGIlmA==
+  dependencies:
+    "@mapbox/point-geometry" "0.1.0"
+    "@mapbox/vector-tile" "^1.3.1"
+    pbf "^3.2.1"
+
 w3c-hr-time@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"