Revise help; add geolocation box; fix map fly to

This commit is contained in:
Logan Williams
2023-04-05 10:34:42 +02:00
parent ba394b3b07
commit d7e6246aa0
8 changed files with 271 additions and 144 deletions

View File

@@ -93,8 +93,8 @@ export default {
name: "FeatureCustom",
data() {
return {
queryTypes: ["point", "line", "polygon"],
selectedQueryType: "point",
queryTypes: ["any", "point", "line", "polygon"],
selectedQueryType: "any",
method: "OR",
filters: [
{

View File

@@ -10,7 +10,7 @@
>
<v-card-title>Selected features</v-card-title>
<v-card-text>
<v-col>
<v-col v-if="$store.state.selected.length > 0">
<FeatureView
v-for="(query, i) in $store.state.selected"
:key="query.name + query.type"
@@ -18,6 +18,11 @@
:index="i"
/>
</v-col>
<v-col v-else>
<v-alert type="info">
Drag and drop feature presets here to add them to the selection.
</v-alert>
</v-col>
</v-card-text>
</v-card>
</v-col>
@@ -32,8 +37,10 @@
query.type == 'point'
? '#8BC34A'
: query.type == 'line'
? '#00BCD4'
: '#FFC107'
? '#46d4db'
: query.type == 'polygon'
? '#FFC107'
: '#BEBEBE'
"
draggable
@dragstart="startDrag($event, query)"

View File

@@ -15,7 +15,12 @@
>
<v-icon x-small>mdi-open-in-new</v-icon></a
>
{{ f.comparison }} {{ f.value }}
{{ f.comparison }}
{{
f.comparison == "is null" || f.comparison == "is not null"
? ""
: f.value
}}
</div>
</div>
</v-card-text>

View File

@@ -0,0 +1,97 @@
<template>
<v-card style="width: 100%">
<v-card-title>Getting started</v-card-title>
<v-card-text :class="{ collapsed: !expanded, help: true }">
<p>
With the OpenStreetMap search tool, a researcher can find geolocation
leads by searching for proximate features on OpenStreetMap.
</p>
<p>
Select features from the list that are found within a certain maximum
distance of each other. Adjust the map to contain the area that you want
to search, and press the search button. Large queries may take a minute
to run to increase the speed search a smaller area by zooming in on
the map. Results can be browsed directly, opened in Google Maps by
clicking the lat/lng, or downloaded as a CSV or KML file.
</p>
<p>
For more information, and a guide to creating searching custom features,
read the associated article (coming soon.)
</p>
<p>
OpenStreetMap is very detailed but accuracy and completeness varies
significantly around the world. This tool can be used to find possible
leads, but it should not be considered exhaustive or used to exclude
areas of interest. For any bugs or new preset requests, contact
<a href="mailto:logan@bellingcat.com">logan@bellingcat.com</a>. >
</p>
</v-card-text>
<div class="readmore" @click="toggle" v-if="!expanded">
<div>Read more help</div>
<!-- <v-btn icon> -->
<v-icon>{{ expanded ? "mdi-chevron-up" : "mdi-chevron-down" }}</v-icon>
<!-- </v-btn> -->
</div>
<div class="readmore" @click="toggle" v-if="expanded">
<div>Close help</div>
<!-- <v-btn icon> -->
<v-icon>{{ expanded ? "mdi-chevron-up" : "mdi-chevron-down" }}</v-icon>
<!-- </v-btn> -->
</div>
</v-card>
</template>
<script>
export default {
name: "HelpCard",
data() {
return {
expanded: false,
};
},
methods: {
toggle() {
this.expanded = !this.expanded;
},
},
};
</script>
<style scoped>
.help.collapsed {
max-height: 48px;
overflow: hidden;
}
.help {
transition: max-height 0.3s;
max-height: 300px;
}
.readmore {
position: absolute;
bottom: -1.5em;
/* background-color: white; */
margin-left: calc(50% - 100px);
width: 200px;
text-align: center;
text-shadow: 0px 0px 4px white;
display: flex;
flex-direction: column;
align-items: center;
background-color: rgba(255, 255, 255, 0.7);
border-radius: 6em;
color: rgb(0, 0, 0, 0.6);
font-size: 0.875rem;
}
.readmore:hover {
cursor: pointer;
/* background-color: rgba(220, 220, 220, 0.9); */
background-color: rgba(255, 255, 255, 0.9);
color: rgb(0, 0, 0, 0.8);
}
</style>

View File

@@ -1,40 +1,7 @@
<template>
<v-container>
<v-row style="padding: 0.75em">
<v-card style="width: 100%">
<v-card-title>Getting started</v-card-title>
<v-card-text>
<p>
With the OpenStreetMap search tool, a researcher can find
geolocation leads by searching for specific objects on
OpenStreetMap.
</p>
<p>
To begin, drag a feature type from the presets list to the "Selected
features" list. Adding multiple features will find only locations
where those features are nearby each other. Set the maximum distance
slider to adjust how far apart the features can be. Adjust the map
to contain the area that you want to search, and press the search
button. Some queries may take several minutes to run. To increase
the speed, zoom in on the map to select a smaller area. Results can
be browsed directly, opened in Google Maps by clicking the lat/lng,
or downloaded as a CSV or KML file.
</p>
<p>
OpenStreetMap is very detailed but accuracy and completeness varies
significantly around the world. This tool can be used to find
possible leads, but it should not be considered exhaustive or used
to exclude areas of interest.
<strong
>Want to search for a type of feature that's not included on the
list?</strong
>
Contact logan@bellingcat.com.
</p>
</v-card-text></v-card
>
<HelpCard />
</v-row>
<feature-selector />
<v-alert
@@ -65,10 +32,11 @@
<v-card style="width: 100%">
<v-card-title>Search area</v-card-title>
<l-map
:zoom.sync="zoom"
:center.sync="center"
:zoom="zoom"
:center="center"
style="width: 100%; height: 600px"
ref="map"
:noBlockingAnimations="true"
>
<l-tile-layer :url="url" />
<l-circle-marker
@@ -95,6 +63,15 @@
:weight="3"
></l-rectangle>
</l-map>
<div class="map-search">
<v-text-field
variant="outlined"
label="Find location"
prepend-inner-icon="mdi-magnify"
@keypress.enter="searchLocation"
v-model="locationSearch"
></v-text-field>
</div>
</v-card>
</v-row>
<v-alert
@@ -128,6 +105,7 @@
<script>
import { LMap, LTileLayer, LCircleMarker, LRectangle } from "vue2-leaflet";
import FeatureSelector from "./FeatureSelector.vue";
import HelpCard from "./HelpCard.vue";
export default {
name: "SearchControls",
@@ -137,6 +115,12 @@ export default {
LCircleMarker,
LRectangle,
FeatureSelector,
HelpCard,
},
data() {
return {
locationSearch: "",
};
},
computed: {
range: {
@@ -151,17 +135,11 @@ export default {
get() {
return this.$store.state.mapCenter;
},
set(val) {
this.$store.commit("setCenter", val);
},
},
zoom: {
get() {
return this.$store.state.mapZoom;
},
set(val) {
this.$store.commit("setZoom", val);
},
},
url() {
if (this.$store.state.mode == "google") {
@@ -198,6 +176,20 @@ export default {
.getElementById("result" + i)
.scrollIntoView({ behavior: "smooth" });
},
searchLocation() {
this.$store.dispatch("searchLocation", this.locationSearch);
this.locationSearch = "";
},
},
};
</script>
<style scoped>
.map-search {
position: absolute;
top: 0.25em;
right: 0.5em;
z-index: 1000;
width: 300px;
}
</style>

View File

@@ -73,8 +73,10 @@ export default {
methods: {
clicked() {
this.$store.commit("setSelectedResult", this.index);
this.$store.commit("setCenter", [this.result.lat, this.result.lng]);
this.$store.commit("setZoom", 13);
this.$store.commit("setMapPosition", {
center: [this.result.lat, this.result.lng],
zoom: 14,
});
},
},
};

View File

@@ -32,84 +32,6 @@ export default [
},
],
},
{
name: "Church",
type: "point",
method: "OR",
filters: [
{
parameter: "amenity",
comparison: "=",
value: "place_of_worship",
},
],
},
{
name: "Hospital",
type: "point",
method: "OR",
filters: [
{
parameter: "amenity",
comparison: "=",
value: "hospital",
},
],
},
{
name: "Military",
type: "point",
method: "OR",
filters: [
{
parameter: "military",
comparison: "is not null",
},
{
parameter: "landuse",
comparison: "=",
value: "military",
},
],
},
{
name: "Restaurant",
type: "point",
method: "OR",
filters: [
{
parameter: "amenity",
comparison: "=",
value: "restaurant",
},
{
parameter: "amenity",
comparison: "=",
value: "cafe",
},
{
parameter: "amenity",
comparison: "=",
value: "pub",
},
{
parameter: "amenity",
comparison: "=",
value: "fast_food",
},
],
},
{
name: "Waterway",
type: "line",
method: "OR",
filters: [
{
parameter: "waterway",
comparison: "is not null",
},
],
},
{
name: "Road",
type: "line",
@@ -342,17 +264,6 @@ export default [
},
],
},
{
name: "Body of water",
type: "polygon",
method: "OR",
filters: [
{
parameter: "water",
comparison: "is not null",
},
],
},
{
name: "Forest",
type: "polygon",
@@ -486,8 +397,32 @@ export default [
],
},
{
name: "Military",
type: "polygon",
name: "Church",
type: "any",
method: "OR",
filters: [
{
parameter: "amenity",
comparison: "=",
value: "place_of_worship",
},
],
},
{
name: "Hospital",
type: "any",
method: "OR",
filters: [
{
parameter: "amenity",
comparison: "=",
value: "hospital",
},
],
},
{
name: "Military use",
type: "any",
method: "OR",
filters: [
{
@@ -501,4 +436,70 @@ export default [
},
],
},
{
name: "Restaurant",
type: "any",
method: "OR",
filters: [
{
parameter: "amenity",
comparison: "=",
value: "restaurant",
},
{
parameter: "amenity",
comparison: "=",
value: "cafe",
},
{
parameter: "amenity",
comparison: "=",
value: "pub",
},
{
parameter: "amenity",
comparison: "=",
value: "fast_food",
},
],
},
{
name: "Convenience store",
type: "any",
method: "OR",
filters: [
{
parameter: "shop",
comparison: "=",
value: "convenience",
},
],
},
{
name: "Fountain",
type: "any",
method: "OR",
filters: [
{
parameter: "amenity",
comparison: "=",
value: "fountain",
},
],
},
{
name: "Water",
type: "any",
method: "OR",
filters: [
{
parameter: "water",
comparison: "is not null",
},
{
parameter: "waterway",
comparison: "is not null",
},
],
},
];

View File

@@ -83,10 +83,8 @@ export default new Vuex.Store({
setError(state, error) {
state.error = error;
},
setCenter(state, center) {
setMapPosition(state, { center, zoom }) {
state.mapCenter = center;
},
setZoom(state, zoom) {
state.mapZoom = zoom;
},
setResponseTime(state, t) {
@@ -147,6 +145,8 @@ export default new Vuex.Store({
let time1 = performance.now();
fetch(
// `http://localhost:5050/intersection?l=${bbox[0][1]}&b=${bbox[0][0]}&r=${bbox[1][1]}&t=${bbox[1][0]}&buffer=${range}&filters=${filters}`,
`https://api.osm-search.bellingcat.com/intersection?l=${bbox[0][1]}&b=${bbox[0][0]}&r=${bbox[1][1]}&t=${bbox[1][0]}&buffer=${range}&filters=${filters}`,
{
headers: {
@@ -184,6 +184,29 @@ export default new Vuex.Store({
}
});
},
searchLocation({ state, commit }, search_text) {
fetch(
`https://api.mapbox.com/geocoding/v5/mapbox.places/${search_text}.json?access_token=pk.eyJ1IjoiYmVsbGluZ2NhdC1tYXBib3giLCJhIjoiY2tleW0wbWliMDA1cTJ5bzdkbTRraHgwZSJ9.GJQkjPzj8554VhR5SPsfJg`
)
.then((d) => {
if (d.status != 200) {
return Promise.reject(Error(d.status));
}
return d.json();
})
.then((data) => {
let maxBounds = Math.max(
data.features[0].bbox[2] - data.features[0].bbox[0],
data.features[0].bbox[3] - data.features[0].bbox[1]
);
console.log(maxBounds, Math.log2(maxBounds), Math.round(Math.log2(maxBounds) + 9));
commit("setMapPosition", {center: [
data.features[0].center[1],
data.features[0].center[0],
], zoom: Math.round(9 - Math.log2(maxBounds))});
});
},
},
modules: {},
});