mirror of
https://github.com/bellingcat/ukraine-timemap.git
synced 2026-06-12 05:18:34 +03:00
add satellite overlay toggle
This commit is contained in:
62
config.js
62
config.js
@@ -2,11 +2,12 @@ const one_day = 1440;
|
||||
module.exports = {
|
||||
title: "ukraine",
|
||||
display_title: "Civilian Harm in Ukraine",
|
||||
SERVER_ROOT: 'https://ukraine.bellingcat.com/ukraine-server',
|
||||
SERVER_ROOT: "https://ukraine.bellingcat.com/ukraine-server",
|
||||
EVENTS_EXT: "/api/ukraine/export_events/deeprows",
|
||||
SOURCES_EXT: "/api/ukraine/export_sources/deepids",
|
||||
ASSOCIATIONS_EXT: "/api/ukraine/export_associations/deeprows",
|
||||
MAPBOX_TOKEN: "pk.eyJ1IjoiYmVsbGluZ2NhdC1tYXBib3giLCJhIjoiY2tleW0wbWliMDA1cTJ5bzdkbTRraHgwZSJ9.GJQkjPzj8554VhR5SPsfJg",
|
||||
MAPBOX_TOKEN:
|
||||
"pk.eyJ1IjoiYmVsbGluZ2NhdC1tYXBib3giLCJhIjoiY2tleW0wbWliMDA1cTJ5bzdkbTRraHgwZSJ9.GJQkjPzj8554VhR5SPsfJg",
|
||||
// MEDIA_EXT: "/api/media",
|
||||
DATE_FMT: "MM/DD/YYYY",
|
||||
TIME_FMT: "HH:mm",
|
||||
@@ -16,7 +17,7 @@ module.exports = {
|
||||
debug: true,
|
||||
map: {
|
||||
// anchor: [49.02421913, 31.43836003],
|
||||
anchor: [48.3326259, 33.199514470],
|
||||
anchor: [48.3326259, 33.19951447],
|
||||
maxZoom: 18,
|
||||
minZoom: 4,
|
||||
startZoom: 6,
|
||||
@@ -24,7 +25,7 @@ module.exports = {
|
||||
},
|
||||
cluster: { radius: 50, minZoom: 5, maxZoom: 12 },
|
||||
associations: {
|
||||
defaultCategory: "Weapon System"
|
||||
defaultCategory: "Weapon System",
|
||||
},
|
||||
timeline: {
|
||||
dimensions: {
|
||||
@@ -32,15 +33,12 @@ module.exports = {
|
||||
contentHeight: 150,
|
||||
},
|
||||
zoomLevels: [
|
||||
{ label: 'Zoom to 1 week', duration: 7 * one_day },
|
||||
{ label: 'Zoom to 2 weeks', duration: 14 * one_day },
|
||||
{ label: 'Zoom to 1 month', duration: 31 * one_day },
|
||||
{ label: 'Zoom to 3 months', duration: 3 * 31 * one_day },
|
||||
],
|
||||
range: [
|
||||
new Date(Date.now() - (14 * (60 * 60 * 1000 * 24))),
|
||||
new Date()
|
||||
{ label: "Zoom to 1 week", duration: 7 * one_day },
|
||||
{ label: "Zoom to 2 weeks", duration: 14 * one_day },
|
||||
{ label: "Zoom to 1 month", duration: 31 * one_day },
|
||||
{ label: "Zoom to 3 months", duration: 3 * 31 * one_day },
|
||||
],
|
||||
range: [new Date(Date.now() - 14 * (60 * 60 * 1000 * 24)), new Date()],
|
||||
// rangeLimits: []
|
||||
},
|
||||
intro: [
|
||||
@@ -71,8 +69,8 @@ module.exports = {
|
||||
"## A Note on Bellingcat's Global Authentication Project",
|
||||
"The Global Authentication Project consists of a wide community of open source researchers assisting in Bellingcat research through structured tasks and feedback. Our aim is to authenticate events taking place around the world and fill in the gaps of knowledge that exist, particularly in situations where there are vast quantities of data. In creating a community for those interested in open source research, we are fostering Bellingcat's original aim of solving problems **together**, to diversify our investigations and promote the use of these skills. For this dataset, we are working with many individuals who have Ukrainian language skills and others with local contextual knowledge of the events and places seen on the map. Other participants include individuals skilled in geolocation and chronolocation, with all contributions being vetted by Bellingcat researchers. As we expand the Global Authentication Project in the coming months, more information will be available on our website and Twitter.",
|
||||
"## Feedback",
|
||||
"This map will continue to change and be updated for the duration of this conflict. We welcome feedback on our methodology, data collection and take transparency seriously. Should you have any direct feedback about the platform, please indicate it on this [form](https://forms.gle/cV2YAojBoh6h4T3XA)."
|
||||
]
|
||||
"This map will continue to change and be updated for the duration of this conflict. We welcome feedback on our methodology, data collection and take transparency seriously. Should you have any direct feedback about the platform, please indicate it on this [form](https://forms.gle/cV2YAojBoh6h4T3XA).",
|
||||
],
|
||||
},
|
||||
toolbar: {
|
||||
panels: {
|
||||
@@ -87,30 +85,43 @@ module.exports = {
|
||||
// label: "Unverified",
|
||||
// description: "todo",
|
||||
// }
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
spotlights: {}
|
||||
spotlights: {},
|
||||
},
|
||||
ui: {
|
||||
coloring: {
|
||||
mode: "STATIC",
|
||||
maxNumOfColors: 9,
|
||||
defaultColor: "#dfdfdf",
|
||||
colors: ["#7E57C2", "#F57C00", "#FFEB3B", "#D34F73", "#08B2E3", "#A1887F", "#90A4AE", "#E57373", "#80CBC4"],
|
||||
colors: [
|
||||
"#7E57C2",
|
||||
"#F57C00",
|
||||
"#FFEB3B",
|
||||
"#D34F73",
|
||||
"#08B2E3",
|
||||
"#A1887F",
|
||||
"#90A4AE",
|
||||
"#E57373",
|
||||
"#80CBC4",
|
||||
],
|
||||
},
|
||||
card: {
|
||||
layout: {
|
||||
template: "sourced"
|
||||
}
|
||||
template: "sourced",
|
||||
},
|
||||
},
|
||||
carto: {
|
||||
eventRadius: 8
|
||||
eventRadius: 8,
|
||||
},
|
||||
timeline: {
|
||||
eventRadius: 9
|
||||
eventRadius: 9,
|
||||
},
|
||||
tiles: {
|
||||
current: "bellingcat-mapbox/cl0qnou2y003m15s8ieuyhgsy",
|
||||
default: "bellingcat-mapbox/cl0qnou2y003m15s8ieuyhgsy",
|
||||
},
|
||||
tiles: 'bellingcat-mapbox/cl0qnou2y003m15s8ieuyhgsy'
|
||||
},
|
||||
features: {
|
||||
USE_CATEGORIES: false,
|
||||
@@ -123,6 +134,7 @@ module.exports = {
|
||||
USE_SHAPES: false,
|
||||
USE_COVER: true,
|
||||
USE_INTRO: false,
|
||||
USE_SATELLITE_OVERLAY_TOGGLE: true,
|
||||
USE_SEARCH: false,
|
||||
USE_SITES: false,
|
||||
ZOOM_TO_TIMEFRAME_ON_TIMELINE_CLICK: one_day,
|
||||
@@ -130,7 +142,7 @@ module.exports = {
|
||||
USE_MEDIA_CACHE: false,
|
||||
GRAPH_NONLOCATED: false,
|
||||
NARRATIVE_STEP_STYLES: false,
|
||||
CUSTOM_EVENT_FIELDS: []
|
||||
CUSTOM_EVENT_FIELDS: [],
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -400,3 +400,17 @@ export function fetchSourceError(msg) {
|
||||
msg,
|
||||
};
|
||||
}
|
||||
|
||||
export const USE_SATELLITE_TILES_OVERLAY = "USE_SATELLITE_TILES_OVERLAY";
|
||||
export function useSatelliteTilesOverlay() {
|
||||
return {
|
||||
type: USE_SATELLITE_TILES_OVERLAY,
|
||||
};
|
||||
}
|
||||
|
||||
export const RESET_TILES_OVERLAY = "RESET_TILES_OVERLAY";
|
||||
export function resetTilesOverlay() {
|
||||
return {
|
||||
type: RESET_TILES_OVERLAY,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
{
|
||||
"es-MX": {
|
||||
"tiles": {
|
||||
"default": "Predeterminado",
|
||||
"satellite": "Satélite"
|
||||
},
|
||||
"loading": "Cargando...",
|
||||
"legend": {
|
||||
"view2d": {
|
||||
@@ -90,6 +94,10 @@
|
||||
}
|
||||
},
|
||||
"en-US": {
|
||||
"tiles": {
|
||||
"default": "Default",
|
||||
"satellite": "Satellite"
|
||||
},
|
||||
"loading": "Loading...",
|
||||
"legend": {
|
||||
"view2d": {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
/* global L */
|
||||
import { bindActionCreators } from "redux";
|
||||
import "leaflet";
|
||||
import React from "react";
|
||||
import { Portal } from "react-portal";
|
||||
@@ -6,6 +7,7 @@ import Supercluster from "supercluster";
|
||||
import { isMobileOnly } from "react-device-detect";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import * as actions from "../../../actions";
|
||||
import * as selectors from "../../../selectors";
|
||||
|
||||
import Sites from "./atoms/Sites";
|
||||
@@ -15,6 +17,7 @@ import Clusters from "./atoms/Clusters";
|
||||
import SelectedEvents from "./atoms/SelectedEvents";
|
||||
import Narratives from "./atoms/Narratives";
|
||||
import DefsMarkers from "./atoms/DefsMarkers";
|
||||
import SatelliteOverlayToggle from "./atoms/SatelliteOverlayToggle";
|
||||
import LoadingOverlay from "../../atoms/Loading";
|
||||
|
||||
import {
|
||||
@@ -41,6 +44,7 @@ class Map extends React.Component {
|
||||
this.svgRef = React.createRef();
|
||||
this.map = null;
|
||||
this.superclusterIndex = null;
|
||||
this.tileLayer = null;
|
||||
this.state = {
|
||||
mapTransformX: 0,
|
||||
mapTransformY: 0,
|
||||
@@ -53,14 +57,22 @@ class Map extends React.Component {
|
||||
componentDidMount() {
|
||||
if (this.map === null) {
|
||||
this.initializeMap();
|
||||
this.initializeTileLayer();
|
||||
}
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.ui.tiles !== this.props.ui.tiles && this.map) {
|
||||
this.initializeTileLayer();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (!isIdentical(nextProps.domain.locations, this.props.domain.locations)) {
|
||||
this.loadClusterData(nextProps.domain.locations);
|
||||
}
|
||||
|
||||
// Set appropriate zoom for narrative
|
||||
const { bounds } = nextProps.app.map;
|
||||
if (!isIdentical(bounds, this.props.app.map.bounds) && bounds !== null) {
|
||||
@@ -92,6 +104,36 @@ class Map extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
initializeTileLayer() {
|
||||
if (!this.map) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
supportedMapboxMap.indexOf(this.props.ui.tiles) !== -1 &&
|
||||
process.env.MAPBOX_TOKEN &&
|
||||
process.env.MAPBOX_TOKEN !== defaultToken
|
||||
) {
|
||||
this.tileLayer = L.tileLayer(
|
||||
`http://a.tiles.mapbox.com/v4/mapbox.${this.props.ui.tiles}/{z}/{x}/{y}@2x.png?access_token=${process.env.MAPBOX_TOKEN}`
|
||||
);
|
||||
} else if (
|
||||
process.env.MAPBOX_TOKEN &&
|
||||
process.env.MAPBOX_TOKEN !== defaultToken
|
||||
) {
|
||||
this.tileLayer = L.tileLayer(
|
||||
`https://api.mapbox.com/styles/v1/${this.props.ui.tiles}/tiles/256/{z}/{x}/{y}@2x?access_token=${process.env.MAPBOX_TOKEN}`
|
||||
// `http://a.tiles.mapbox.com/styles/v1/${this.props.ui.tiles}/tiles/{z}/{x}/{y}?access_token=${process.env.MAPBOX_TOKEN}`
|
||||
);
|
||||
} else {
|
||||
this.tileLayer = L.tileLayer(
|
||||
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
// "https://api.maptiler.com/maps/bright/256/{z}/{x}/{y}.png?key="
|
||||
);
|
||||
}
|
||||
this.tileLayer.addTo(this.map);
|
||||
}
|
||||
|
||||
initializeMap() {
|
||||
/**
|
||||
* Creates a Leaflet map and a tilelayer for the map background
|
||||
@@ -111,31 +153,7 @@ class Map extends React.Component {
|
||||
// Initialize supercluster index
|
||||
this.superclusterIndex = new Supercluster(clusterConfig);
|
||||
|
||||
let firstLayer;
|
||||
|
||||
if (
|
||||
supportedMapboxMap.indexOf(this.props.ui.tiles) !== -1 &&
|
||||
process.env.MAPBOX_TOKEN &&
|
||||
process.env.MAPBOX_TOKEN !== defaultToken
|
||||
) {
|
||||
firstLayer = L.tileLayer(
|
||||
`http://a.tiles.mapbox.com/v4/mapbox.${this.props.ui.tiles}/{z}/{x}/{y}@2x.png?access_token=${process.env.MAPBOX_TOKEN}`
|
||||
);
|
||||
} else if (
|
||||
process.env.MAPBOX_TOKEN &&
|
||||
process.env.MAPBOX_TOKEN !== defaultToken
|
||||
) {
|
||||
firstLayer = L.tileLayer(
|
||||
`https://api.mapbox.com/styles/v1/${this.props.ui.tiles}/tiles/256/{z}/{x}/{y}@2x?access_token=${process.env.MAPBOX_TOKEN}`
|
||||
// `http://a.tiles.mapbox.com/styles/v1/${this.props.ui.tiles}/tiles/{z}/{x}/{y}?access_token=${process.env.MAPBOX_TOKEN}`
|
||||
);
|
||||
} else {
|
||||
firstLayer = L.tileLayer(
|
||||
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
|
||||
// "https://api.maptiler.com/maps/bright/256/{z}/{x}/{y}.png?key="
|
||||
);
|
||||
}
|
||||
firstLayer.addTo(map);
|
||||
this.initializeTileLayer(map);
|
||||
|
||||
map.keyboard.disable();
|
||||
map.zoomControl.remove();
|
||||
@@ -517,6 +535,13 @@ class Map extends React.Component {
|
||||
ui={isFetchingDomain}
|
||||
language={this.props.app.language}
|
||||
/>
|
||||
{this.props.features.USE_SATELLITE_OVERLAY_TOGGLE && (
|
||||
<SatelliteOverlayToggle
|
||||
isUsingSatellite={this.props.ui.tiles === "satellite"}
|
||||
switchToSatellite={this.props.actions.useSatelliteTilesOverlay}
|
||||
reset={this.props.actions.resetTilesOverlay}
|
||||
/>
|
||||
)}
|
||||
{innerMap}
|
||||
</div>
|
||||
);
|
||||
@@ -548,7 +573,7 @@ function mapStateToProps(state) {
|
||||
},
|
||||
},
|
||||
ui: {
|
||||
tiles: state.ui.tiles,
|
||||
tiles: state.ui.tiles.current,
|
||||
dom: state.ui.dom,
|
||||
narratives: state.ui.style.narratives,
|
||||
mapSelectedEvents: state.ui.style.selectedEvents,
|
||||
@@ -561,4 +586,10 @@ function mapStateToProps(state) {
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(Map);
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators(actions, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Map);
|
||||
|
||||
40
src/components/space/carto/atoms/SatelliteOverlayToggle.js
Normal file
40
src/components/space/carto/atoms/SatelliteOverlayToggle.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from "react";
|
||||
import copy from "../../../../common/data/copy.json";
|
||||
import { language } from "../../../../common/utilities";
|
||||
|
||||
const SatelliteOverlayToggle = ({
|
||||
switchToSatellite,
|
||||
reset,
|
||||
isUsingSatellite,
|
||||
}) => {
|
||||
return (
|
||||
<div id="satellite-overlay-toggle" className="satellite-overlay-toggle">
|
||||
<button
|
||||
disabled={!isUsingSatellite}
|
||||
id="satellite-overlay-toggle-default"
|
||||
className={
|
||||
!isUsingSatellite
|
||||
? "satellite-overlay-toggle-button-active"
|
||||
: "satellite-overlay-toggle-button-inactive"
|
||||
}
|
||||
onClick={reset}
|
||||
>
|
||||
{copy[language].tiles.default}
|
||||
</button>
|
||||
<button
|
||||
id="satellite-overlay-toggle-satellite"
|
||||
className={
|
||||
isUsingSatellite
|
||||
? "satellite-overlay-toggle-button-active"
|
||||
: "satellite-overlay-toggle-button-inactive"
|
||||
}
|
||||
disabled={isUsingSatellite}
|
||||
onClick={switchToSatellite}
|
||||
>
|
||||
{copy[language].tiles.satellite}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SatelliteOverlayToggle;
|
||||
@@ -1,9 +1,28 @@
|
||||
import initial from "../store/initial.js";
|
||||
|
||||
import {} from "../actions";
|
||||
import { USE_SATELLITE_TILES_OVERLAY, RESET_TILES_OVERLAY } from "../actions";
|
||||
|
||||
function ui(uiState = initial.ui, action) {
|
||||
return uiState;
|
||||
switch (action.type) {
|
||||
case USE_SATELLITE_TILES_OVERLAY:
|
||||
return {
|
||||
...uiState,
|
||||
tiles: {
|
||||
...uiState.tiles,
|
||||
current: "satellite",
|
||||
},
|
||||
};
|
||||
case RESET_TILES_OVERLAY:
|
||||
return {
|
||||
...uiState,
|
||||
tiles: {
|
||||
...uiState.tiles,
|
||||
current: uiState.tiles.default,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return uiState;
|
||||
}
|
||||
}
|
||||
|
||||
export default ui;
|
||||
|
||||
@@ -13,3 +13,4 @@
|
||||
@import "mediaplayer";
|
||||
@import "cover";
|
||||
@import "search";
|
||||
@import "satelliteoverlaytoggle";
|
||||
|
||||
28
src/scss/satelliteoverlaytoggle.scss
Normal file
28
src/scss/satelliteoverlaytoggle.scss
Normal file
@@ -0,0 +1,28 @@
|
||||
@import "variables";
|
||||
|
||||
.satellite-overlay-toggle {
|
||||
background-color: rgb(53, 53, 53);
|
||||
opacity: 0.9;
|
||||
position: fixed;
|
||||
top: 4px;
|
||||
left: 120px;
|
||||
z-index: $map-overlay;
|
||||
}
|
||||
|
||||
.satellite-overlay-toggle-button-active {
|
||||
padding: 2px 4px 4px 4px;
|
||||
border-style: none;
|
||||
font-size: $small;
|
||||
font-family: $mainfont;
|
||||
background-color: rgb(53, 53, 53);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.satellite-overlay-toggle-button-inactive {
|
||||
padding: 2px 4px 4px 4px;
|
||||
border-style: none;
|
||||
font-size: $small;
|
||||
font-family: $mainfont;
|
||||
background-color: rgb(53, 53, 53);
|
||||
color: rgb(159, 159, 159);
|
||||
}
|
||||
@@ -149,7 +149,10 @@ const initial = {
|
||||
* as well as dom elements to attach SVG
|
||||
*/
|
||||
ui: {
|
||||
tiles: "openstreetmap", // ['openstreetmap', 'streets', 'satellite']
|
||||
tiles: {
|
||||
current: "openstreetmap", // ['openstreetmap', 'streets', 'satellite']
|
||||
default: "openstreetmap", // ['openstreetmap', 'streets', 'satellite']
|
||||
},
|
||||
style: {
|
||||
categories: {
|
||||
default: global.fallbackEventColor,
|
||||
|
||||
Reference in New Issue
Block a user