mirror of
https://github.com/bellingcat/osm-search.git
synced 2026-06-08 03:28:33 +03:00
Revise help; add geolocation box; fix map fly to
This commit is contained in:
@@ -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: [
|
||||
{
|
||||
|
||||
@@ -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)"
|
||||
|
||||
@@ -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>
|
||||
|
||||
97
frontend/src/components/HelpCard.vue
Normal file
97
frontend/src/components/HelpCard.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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: {},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user