mirror of
https://github.com/Einundzwanzig-Podcast/einundzwanzig-portal.git
synced 2025-12-11 06:46:47 +00:00
merge with geojson easify repo
This commit is contained in:
@@ -23,7 +23,7 @@ SESSION_LIFETIME=120
|
||||
|
||||
MEMCACHED_HOST=127.0.0.1
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_HOST=redis
|
||||
REDIS_PASSWORD=null
|
||||
REDIS_PORT=6379
|
||||
|
||||
@@ -55,3 +55,5 @@ VITE_PUSHER_HOST="${PUSHER_HOST}"
|
||||
VITE_PUSHER_PORT="${PUSHER_PORT}"
|
||||
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
|
||||
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
|
||||
|
||||
CIPHERSWEET_KEY=b130a919cba9b7745d4b0144533308f2ffa38476cf562175aab8ca9f0e9648f1
|
||||
|
||||
12
README.md
12
README.md
@@ -20,21 +20,21 @@ docker run --rm \
|
||||
|
||||
```vendor/bin/sail up -d```
|
||||
|
||||
### Migrate and seed the database
|
||||
|
||||
```./vendor/bin/sail artisan migrate:fresh --seed```
|
||||
|
||||
#### Install node dependencies
|
||||
|
||||
```vendor/bin/sail yarn install```
|
||||
|
||||
#### Start compiling watcher
|
||||
#### Start just in time compiler
|
||||
|
||||
```vendor/bin/sail yarn dev```
|
||||
|
||||
#### Compile assets
|
||||
|
||||
```vendor/bin/sail yarn build```
|
||||
|
||||
#### Update dependencies
|
||||
|
||||
```vendor/bin/sail yarn install```
|
||||
```vendor/bin/sail yarn```
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
namespace App\Http\Livewire\Meetup;
|
||||
|
||||
use App\Models\Meetup;
|
||||
use Illuminate\Http\Client\Pool;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Livewire\Component;
|
||||
@@ -14,138 +15,322 @@ class PrepareForBtcMapItem extends Component
|
||||
|
||||
public Meetup $meetup;
|
||||
|
||||
public $model;
|
||||
|
||||
public string $search = '';
|
||||
|
||||
public $population;
|
||||
public $population_date;
|
||||
public $osmSearchResults;
|
||||
public $osmSearchResultsState;
|
||||
public $osmSearchResultsCountry;
|
||||
public $population_date = '';
|
||||
|
||||
public $selectedItem;
|
||||
public ?int $osm_id = null;
|
||||
|
||||
public function rules()
|
||||
public array $osmSearchResults = [];
|
||||
|
||||
public $selectedItemOSMPolygons;
|
||||
|
||||
public $selectedItemOSMBoundaries;
|
||||
|
||||
public $selectedItemPolygonsOSMfr;
|
||||
|
||||
public $polygonsOSMfrX = 0.020000;
|
||||
|
||||
public $polygonsOSMfrY = 0.005000;
|
||||
|
||||
public $polygonsOSMfrZ = 0.005000;
|
||||
|
||||
public $currentPercentage = 100;
|
||||
|
||||
public bool $OSMBoundaries = false;
|
||||
|
||||
public bool $polygonsOSMfr = false;
|
||||
|
||||
protected $queryString = [
|
||||
'search' => ['except' => ''],
|
||||
'osm_id' => ['except' => null],
|
||||
];
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'population' => 'required',
|
||||
'search' => 'required|string',
|
||||
'currentPercentage' => 'required|numeric',
|
||||
|
||||
'model.simplified_geojson' => 'nullable',
|
||||
|
||||
'OSMBoundaries' => 'bool',
|
||||
'polygonsOSMfr' => 'bool',
|
||||
|
||||
'population' => 'required|numeric',
|
||||
'population_date' => 'required|string',
|
||||
|
||||
'polygonsOSMfrX' => 'numeric|max:1',
|
||||
'polygonsOSMfrY' => 'numeric|max:1',
|
||||
'polygonsOSMfrZ' => 'numeric|max:1',
|
||||
];
|
||||
}
|
||||
|
||||
public function mount()
|
||||
public function mount(): void
|
||||
{
|
||||
$response = Http::acceptJson()
|
||||
->get(
|
||||
'https://nominatim.openstreetmap.org/search?city='.$this->meetup->city->name.'&format=json&polygon_geojson=1'
|
||||
);
|
||||
$this->osmSearchResults = $response->json();
|
||||
|
||||
$response = Http::acceptJson()
|
||||
->get(
|
||||
'https://nominatim.openstreetmap.org/search?state='.$this->meetup->city->name.'&format=json&polygon_geojson=1'
|
||||
);
|
||||
$this->osmSearchResultsState = $response->json();
|
||||
|
||||
$response = Http::acceptJson()
|
||||
->get(
|
||||
'https://nominatim.openstreetmap.org/search?country='.$this->meetup->city->name.'&format=json&polygon_geojson=1'
|
||||
);
|
||||
$this->osmSearchResultsCountry = $response->json();
|
||||
|
||||
if ($this->meetup->city->osm_relation) {
|
||||
$this->selectedItem = $this->meetup->city->osm_relation;
|
||||
$this->model = $this->meetup->city;
|
||||
$this->population = $this->model->population;
|
||||
$this->population_date = $this->model->population_date;
|
||||
$this->getSearchResults();
|
||||
if ($this->osm_id) {
|
||||
$this->selectedItemOSMPolygons = collect($this->osmSearchResults)
|
||||
->firstWhere('osm_id', $this->osm_id);
|
||||
$this->executeMapshaper($this->currentPercentage);
|
||||
}
|
||||
$this->population = $this->meetup->city->population;
|
||||
$this->population_date = $this->meetup->city->population_date;
|
||||
}
|
||||
|
||||
private function getSearchResults(): void
|
||||
{
|
||||
$responses = Http::pool(fn(Pool $pool) => [
|
||||
$pool->acceptJson()
|
||||
->get(
|
||||
sprintf('https://nominatim.openstreetmap.org/search?q=%s&format=json&polygon_geojson=1&polygon_threshold=0.0003&email='.config('services.nominatim.email'),
|
||||
$this->search)
|
||||
),
|
||||
]);
|
||||
|
||||
$this->osmSearchResults = collect($responses[0]->json())
|
||||
->filter(fn($item
|
||||
) => (
|
||||
$item['geojson']['type'] === 'Polygon'
|
||||
|| $item['geojson']['type'] === 'MultiPolygon'
|
||||
)
|
||||
&& $item['osm_id']
|
||||
&& count($item['geojson']['coordinates'], COUNT_RECURSIVE) < 100000
|
||||
)
|
||||
->values()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
private function executeMapshaper($percentage = 100): void
|
||||
{
|
||||
try {
|
||||
// put OSM geojson to storage
|
||||
Storage::disk('geo')
|
||||
->put('geojson_'.$this->selectedItemOSMPolygons['osm_id'].'.json',
|
||||
json_encode($this->selectedItemOSMPolygons['geojson'], JSON_THROW_ON_ERROR)
|
||||
);
|
||||
|
||||
// execute mapshaper
|
||||
$input = storage_path('app/geo/geojson_'.$this->selectedItemOSMPolygons['osm_id'].'.json');
|
||||
$output = storage_path('app/geo/output_'.$this->selectedItemOSMPolygons['osm_id'].'.json');
|
||||
$mapShaperBinary = base_path('node_modules/mapshaper/bin/mapshaper');
|
||||
exec($mapShaperBinary.' '.$input.' -simplify dp '.$percentage.'% -o '.$output);
|
||||
$this->currentPercentage = $percentage;
|
||||
|
||||
$mapShaperOutput = str(
|
||||
Storage::disk('geo')
|
||||
->get('output_'.$this->selectedItemOSMPolygons['osm_id'].'.json')
|
||||
);
|
||||
if ($mapShaperOutput->contains(['Polygon', 'MultiPolygon'])) {
|
||||
// trim geojson
|
||||
Storage::disk('geo')
|
||||
->put(
|
||||
'trimmed_'.$this->selectedItemOSMPolygons['osm_id'].'.json',
|
||||
$mapShaperOutput
|
||||
->after('{"type":"GeometryCollection", "geometries": [')
|
||||
->beforeLast(']}')
|
||||
->toString()
|
||||
);
|
||||
} else {
|
||||
$this->notification()
|
||||
->warning('Warning',
|
||||
sprintf('Geojson is not valid. After simplification, it contains no polygons. Instead it contains: %s',
|
||||
$mapShaperOutput->after('{"type":')
|
||||
->before(',')));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// put trimmed geojson to model
|
||||
$this->model->simplified_geojson = json_decode(
|
||||
trim(
|
||||
Storage::disk('geo')
|
||||
->get('trimmed_'.$this->selectedItemOSMPolygons['osm_id'].'.json')
|
||||
),
|
||||
false, 512, JSON_THROW_ON_ERROR
|
||||
);
|
||||
|
||||
// emit event for AlpineJS
|
||||
$this->emit('geoJsonUpdated');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
$this->notification()
|
||||
->error('Error', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function saveSimplifiedGeoJson()
|
||||
{
|
||||
$this->model->osm_relation = $this->osm_id;
|
||||
$this->model->save();
|
||||
|
||||
$this->notification()
|
||||
->success('Success', 'Simplified GeoJSON saved.');
|
||||
}
|
||||
|
||||
public function submitPolygonsOSM()
|
||||
{
|
||||
$this->validate();
|
||||
$postGenerate = Http::acceptJson()
|
||||
->asForm()
|
||||
->post(
|
||||
'https://polygons.openstreetmap.fr/?id='.$this->selectedItemOSMPolygons['osm_id'],
|
||||
[
|
||||
'x' => $this->polygonsOSMfrX,
|
||||
'y' => $this->polygonsOSMfrY,
|
||||
'z' => $this->polygonsOSMfrZ,
|
||||
'generate' => 'Submit+Query',
|
||||
]
|
||||
);
|
||||
if ($postGenerate->ok()) {
|
||||
$getUrl = sprintf(
|
||||
'https://polygons.openstreetmap.fr/get_geojson.py?id=%s¶ms=%s-%s-%s',
|
||||
$this->selectedItemOSMPolygons['osm_id'],
|
||||
(float) str($this->polygonsOSMfrX)
|
||||
->before('.')
|
||||
->toString().'.'.str($this->polygonsOSMfrX)
|
||||
->after('.')
|
||||
->padRight(6, '0')
|
||||
->toString(),
|
||||
(float) str($this->polygonsOSMfrY)
|
||||
->before('.')
|
||||
->toString().'.'.str($this->polygonsOSMfrY)
|
||||
->after('.')
|
||||
->padRight(6, '0')
|
||||
->toString(),
|
||||
(float) str($this->polygonsOSMfrZ)
|
||||
->before('.')
|
||||
->toString().'.'.str($this->polygonsOSMfrZ)
|
||||
->after('.')
|
||||
->padRight(6, '0')
|
||||
->toString(),
|
||||
);
|
||||
$response = Http::acceptJson()
|
||||
->get($getUrl);
|
||||
if ($response->json()) {
|
||||
$this->selectedItemPolygonsOSMfr = $response->json();
|
||||
$this->emit('geoJsonUpdated');
|
||||
} else {
|
||||
$this->notification()
|
||||
->warning('No data', 'No data found for this area.');
|
||||
}
|
||||
} else {
|
||||
$this->notification()
|
||||
->error('Error', 'Something went wrong: '.$postGenerate->status());
|
||||
}
|
||||
}
|
||||
|
||||
public function submit(): void
|
||||
{
|
||||
$this->validate();
|
||||
$this->getSearchResults();
|
||||
}
|
||||
|
||||
public function selectItem($index): void
|
||||
{
|
||||
$this->OSMBoundaries = false;
|
||||
$this->selectedItemOSMBoundaries = null;
|
||||
$this->selectedItemOSMPolygons = $this->osmSearchResults[$index];
|
||||
$this->osm_id = $this->selectedItemOSMPolygons['osm_id'];
|
||||
$this->model->osm_relation = $this->selectedItemOSMPolygons;
|
||||
|
||||
$this->executeMapshaper(100);
|
||||
}
|
||||
|
||||
public function updatedOSMBoundaries($value)
|
||||
{
|
||||
if ($value) {
|
||||
$response = Http::acceptJson()
|
||||
->asForm()
|
||||
->post('https://osm-boundaries.com/Ajax/GetBoundary', [
|
||||
'db' => 'osm20221205',
|
||||
'waterOrLand' => 'water',
|
||||
'osmId' => '-'.$this->selectedItemOSMPolygons['osm_id'],
|
||||
]);
|
||||
if ($response->json()) {
|
||||
if (count($response->json()['coordinates'], COUNT_RECURSIVE) > 100000) {
|
||||
$this->notification()
|
||||
->warning('Warning', 'Water boundaries are too big');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->selectedItemOSMBoundaries = $response->json();
|
||||
$this->emit('geoJsonUpdated');
|
||||
} else {
|
||||
$this->notification()
|
||||
->warning('Warning', 'No water boundaries found');
|
||||
}
|
||||
} else {
|
||||
$this->selectedItemOSMBoundaries = null;
|
||||
$this->emit('geoJsonUpdated');
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedCurrentPercentage($value)
|
||||
{
|
||||
$this->executeMapshaper((float) $value);
|
||||
}
|
||||
|
||||
public function setPercentage($percent): void
|
||||
{
|
||||
$this->executeMapshaper($percent);
|
||||
}
|
||||
|
||||
public function updatedPopulation($value)
|
||||
{
|
||||
$value = str($value)
|
||||
->replace('.', '')
|
||||
->replace(',', '.')
|
||||
->toInteger();
|
||||
$this->meetup->city->population = $value;
|
||||
$this->population = $value;
|
||||
$this->meetup->city->save();
|
||||
$this->model->population = (int) str($value)
|
||||
->replace(['.', ','], '')
|
||||
->toString();
|
||||
$this->model->save();
|
||||
|
||||
$this->notification()
|
||||
->success('Population updated', 'Success');
|
||||
->success('Success', 'Population saved.');
|
||||
}
|
||||
|
||||
public function updatedPopulationDate($value)
|
||||
{
|
||||
$this->meetup->city->population_date = $value;
|
||||
$this->meetup->city->save();
|
||||
$this->model->population_date = $value;
|
||||
$this->model->save();
|
||||
|
||||
$this->notification()
|
||||
->success('Population date updated', 'Success');
|
||||
}
|
||||
|
||||
public function selectItem($index, bool $isState = false, $isCountry = false)
|
||||
{
|
||||
if ($isState) {
|
||||
$this->selectedItem = $this->osmSearchResultsState[$index];
|
||||
} elseif ($isCountry) {
|
||||
$this->selectedItem = $this->osmSearchResultsCountry[$index];
|
||||
} else {
|
||||
$this->selectedItem = $this->osmSearchResults[$index];
|
||||
}
|
||||
Storage::disk('geo')
|
||||
->put('geojson_'.$this->selectedItem['osm_id'].'.json',
|
||||
json_encode($this->selectedItem['geojson'], JSON_THROW_ON_ERROR));
|
||||
$input = storage_path('app/geo/geojson_'.$this->selectedItem['osm_id'].'.json');
|
||||
$output = storage_path('app/geo/output_'.$this->selectedItem['osm_id'].'.json');
|
||||
exec('mapshaper '.$input.' -simplify dp 4% -o '.$output);
|
||||
Storage::disk('geo')
|
||||
->put(
|
||||
'trimmed_'.$this->selectedItem['osm_id'].'.json',
|
||||
str(Storage::disk('geo')
|
||||
->get('output_'.$this->selectedItem['osm_id'].'.json'))
|
||||
->after('{"type":"GeometryCollection", "geometries": [')
|
||||
->beforeLast(']}')
|
||||
->toString()
|
||||
);
|
||||
$this->meetup->city->osm_relation = $this->selectedItem;
|
||||
$this->meetup->city->simplified_geojson = json_decode(trim(Storage::disk('geo')
|
||||
->get('trimmed_'.$this->selectedItem['osm_id'].'.json')),
|
||||
false, 512, JSON_THROW_ON_ERROR);
|
||||
$this->meetup->city->population = 0;
|
||||
$this->meetup->city->population_date = '2021-12-31';
|
||||
$this->meetup->city->save();
|
||||
|
||||
return to_route('osm.meetups.item', ['meetup' => $this->meetup]);
|
||||
}
|
||||
|
||||
public function setPercent($percent)
|
||||
{
|
||||
$input = storage_path('app/geo/geojson_'.$this->selectedItem['osm_id'].'.json');
|
||||
$output = storage_path('app/geo/output_'.$this->selectedItem['osm_id'].'.json');
|
||||
exec('mapshaper '.$input.' -simplify dp '.$percent.'% -o '.$output);
|
||||
Storage::disk('geo')
|
||||
->put(
|
||||
'trimmed_'.$this->selectedItem['osm_id'].'.json',
|
||||
str(Storage::disk('geo')
|
||||
->get('output_'.$this->selectedItem['osm_id'].'.json'))
|
||||
->after('{"type":"GeometryCollection", "geometries": [')
|
||||
->beforeLast(']}')
|
||||
->toString()
|
||||
);
|
||||
$this->meetup->city->simplified_geojson = json_decode(trim(Storage::disk('geo')
|
||||
->get('trimmed_'.$this->selectedItem['osm_id'].'.json')),
|
||||
false, 512, JSON_THROW_ON_ERROR);
|
||||
$this->meetup->city->save();
|
||||
|
||||
return to_route('osm.meetups.item', ['meetup' => $this->meetup]);
|
||||
}
|
||||
|
||||
public function takePop($value)
|
||||
{
|
||||
$this->meetup->city->population = $value;
|
||||
$this->meetup->city->population_date = '2021-12-31';
|
||||
$this->meetup->city->save();
|
||||
|
||||
return to_route('osm.meetups.item', ['meetup' => $this->meetup]);
|
||||
->success('Success', 'Population saved.');
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.meetup.prepare-for-btc-map-item');
|
||||
return view('livewire.meetup.prepare-for-btc-map-item', [
|
||||
'percentages' => collect([
|
||||
0.5,
|
||||
0.75,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
15,
|
||||
20,
|
||||
25,
|
||||
30,
|
||||
40,
|
||||
50,
|
||||
60,
|
||||
75,
|
||||
80,
|
||||
100,
|
||||
])
|
||||
->reverse()
|
||||
->values()
|
||||
->toArray(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ class MeetupForBtcMapTable extends DataTableComponent
|
||||
'simplified_geojson',
|
||||
'population',
|
||||
'population_date',
|
||||
'city_id',
|
||||
])
|
||||
->setPerPageAccepted([
|
||||
100000,
|
||||
|
||||
@@ -30,9 +30,10 @@ class Meetup extends Model implements HasMedia
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'id' => 'integer',
|
||||
'city_id' => 'integer',
|
||||
'github_data' => 'json',
|
||||
'id' => 'integer',
|
||||
'city_id' => 'integer',
|
||||
'github_data' => 'json',
|
||||
'simplified_geojson' => 'array',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<x-button
|
||||
xs
|
||||
amber
|
||||
:href="route('osm.meetups.item', ['meetup' => $row])"
|
||||
:href="route('osm.meetups.item', ['meetup' => $row, 'search' => $row->city->name])"
|
||||
>
|
||||
Open OSM Item
|
||||
</x-button>
|
||||
|
||||
@@ -1,118 +1,444 @@
|
||||
<div class="p-6 w-full" wire:loading.class="opacity-50">
|
||||
<div class="max-w-none text-white flex flex-col space-y-4">
|
||||
<a href="{{ route('osm.meetups') }}">Zurück</a>
|
||||
<div class="grid grid-cols-3 gap-2">
|
||||
<div>
|
||||
<h1>Search city: {{ $meetup->city->name }}</h1>
|
||||
<h1>OSM API Response</h1>
|
||||
<div class="flex flex-col space-y-2">
|
||||
@foreach($osmSearchResults as $item)
|
||||
<code class="w-full">
|
||||
<div wire:key="osmItemCity_{{ $loop->index }}" class="cursor-pointer underline"
|
||||
wire:click="selectItem({{ $loop->index }})">
|
||||
{{ $item['display_name'] }}
|
||||
</div>
|
||||
</code>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h1>Search state: {{ $meetup->city->name }}</h1>
|
||||
<h1>OSM API Response</h1>
|
||||
<div class="flex flex-col space-y-2">
|
||||
@foreach($osmSearchResultsState as $item)
|
||||
<code class="w-full">
|
||||
<div wire:key="osmItemState_{{ $loop->index }}" class="cursor-pointer underline"
|
||||
wire:click="selectItem({{ $loop->index }}, true)">
|
||||
{{ $item['display_name'] }}
|
||||
</div>
|
||||
</code>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h1>Search country: {{ $meetup->city->name }}</h1>
|
||||
<h1>OSM API Response</h1>
|
||||
<div class="flex flex-col space-y-2">
|
||||
@foreach($osmSearchResultsCountry as $item)
|
||||
<code class="w-full">
|
||||
<div wire:key="osmItemCountry_{{ $loop->index }}" class="cursor-pointer underline"
|
||||
wire:click="selectItem({{ $loop->index }}, false, true)">
|
||||
{{ $item['display_name'] }}
|
||||
</div>
|
||||
</code>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@if($selectedItem)
|
||||
geojson created
|
||||
@endif
|
||||
</div>
|
||||
<h1>Current data [points: {{ count($meetup->city->simplified_geojson['coordinates'][0] ?? []) }}]</h1>
|
||||
<div class="flex space-x-2">
|
||||
<div class="cursor-pointer font-bold underline" wire:click="setPercent(7)">7%</div>
|
||||
<div class="cursor-pointer font-bold underline" wire:click="setPercent(6)">6%</div>
|
||||
<div class="cursor-pointer font-bold underline" wire:click="setPercent(5)">5%</div>
|
||||
<div class="cursor-pointer font-bold underline" wire:click="setPercent(4)">4%</div>
|
||||
<div class="cursor-pointer font-bold underline" wire:click="setPercent(3)">3%</div>
|
||||
<div class="cursor-pointer font-bold underline" wire:click="setPercent(2)">2%</div>
|
||||
<div class="cursor-pointer font-bold underline" wire:click="setPercent(1)">1%</div>
|
||||
<div class="cursor-pointer font-bold underline" wire:click="setPercent(0.75)">0.75%</div>
|
||||
<div class="cursor-pointer font-bold underline" wire:click="setPercent(0.5)">0.5%</div>
|
||||
</div>
|
||||
<div>
|
||||
@if($meetup->city->simplified_geojson)
|
||||
<h1>Simplified geojson</h1>
|
||||
<pre
|
||||
class="overflow-x-auto py-4">{{ json_encode($meetup->city->simplified_geojson, JSON_THROW_ON_ERROR) }}</pre>
|
||||
<div wire:ignore
|
||||
class="my-4"
|
||||
x-data="{
|
||||
init() {
|
||||
var map = L.map($refs.map)
|
||||
.setView([{{ $meetup->city->longitude }}, {{ $meetup->city->latitude }}], 13);
|
||||
<div>
|
||||
<div class="w-full p-0 lg:p-6" wire:loading.class="opacity-50 pointer-events-none cursor-not-allowed">
|
||||
<div class="flex max-w-none flex-col space-y-4 text-black">
|
||||
|
||||
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png').addTo(map);
|
||||
{{-- SEARCH PANEL --}}
|
||||
<div class="rounded-lg bg-white shadow dark:bg-gray-800">
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2">
|
||||
<div class="px-4 py-5 lg:p-6">
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">Search for an area
|
||||
</h3>
|
||||
<div class="mt-2 text-sm text-gray-500">
|
||||
|
||||
var geojsonFeature = {
|
||||
'type': 'Feature',
|
||||
'geometry': @js($meetup->city->simplified_geojson)
|
||||
};
|
||||
console.log(geojsonFeature);
|
||||
L.geoJSON(geojsonFeature).addTo(map);
|
||||
let geoJSON = L.geoJson(geojsonFeature).addTo(map);
|
||||
map.fitBounds(geoJSON.getBounds());
|
||||
}
|
||||
}">
|
||||
<div x-ref="map" style="width: 80vw; height: 30vh;"></div>
|
||||
<form wire:submit.prevent="submit" class="space-y-2">
|
||||
@if (!$model?->simplified_geojson || !$selectedItemOSMPolygons)
|
||||
<div class="flex flex-col space-y-2 lg:flex-row lg:space-y-0 lg:space-x-2">
|
||||
<div>
|
||||
<x-input wire:model.defer="search"/>
|
||||
</div>
|
||||
<div>
|
||||
<x-button type="submit" class='w-full font-semibold'>Search</x-button>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="flex flex-col space-y-2">
|
||||
<a href="{{ route('osm.meetups') }}">
|
||||
<x-badge gray class="whitespace-nowrap dark:bg-gray-200 dark:text-black">
|
||||
Back
|
||||
</x-badge>
|
||||
</a>
|
||||
<div class="overflow-hidden rounded-lg bg-white shadow dark:bg-gray-900">
|
||||
<div class="px-2 py-2 sm:px-4 sm:py-5 sm:px-6">
|
||||
<h3
|
||||
class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-200">
|
||||
{{ $selectedItemOSMPolygons['display_name'] }}
|
||||
</h3>
|
||||
<p class="mt-1 max-w-2xl text-sm text-gray-500 dark:text-gray-300">
|
||||
|
||||
</p>
|
||||
</div>
|
||||
<div class="border-t border-gray-200 px-2 py-2 sm:p-0 sm:px-4 sm:py-5">
|
||||
<dl class="sm:divide-y sm:divide-gray-200">
|
||||
<div class="space-y-1 py-2 sm:py-4 sm:py-5">
|
||||
<dt
|
||||
class="text-sm font-medium text-gray-500 dark:text-gray-300">
|
||||
<x-badge
|
||||
blue>{{ $selectedItemOSMPolygons['type'] }}</x-badge>
|
||||
</dt>
|
||||
<dd class="text-sm text-gray-900 dark:text-gray-300">
|
||||
OSM ID: {{ $selectedItemOSMPolygons['osm_id'] }}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-hidden rounded-lg bg-white shadow dark:bg-gray-900">
|
||||
<div class="px-2 py-2 sm:px-4 sm:py-5 sm:px-6">
|
||||
<div class="mt-1 max-w-2xl text-sm text-gray-500 dark:text-gray-300">
|
||||
<x-toggle red lg
|
||||
label="Fetch water boundaries from https://osm-boundaries.com"
|
||||
wire:model="OSMBoundaries"/>
|
||||
</div>
|
||||
<div x-data="{
|
||||
show: @entangle('polygonsOSMfr')
|
||||
}"
|
||||
class="mt-2 max-w-2xl text-sm text-gray-500 dark:text-gray-300">
|
||||
<x-toggle red lg
|
||||
label="Fetch polygons from https://polygons.openstreetmap.fr"
|
||||
wire:model="polygonsOSMfr"/>
|
||||
<div class="mt-2 flex flex-row items-end space-x-2" x-show="show">
|
||||
<x-input max="1" label="X"
|
||||
wire:model.defer="polygonsOSMfrX"/>
|
||||
<x-input max="1" label="Y"
|
||||
wire:model.defer="polygonsOSMfrY"/>
|
||||
<x-input max="1" label="Z"
|
||||
wire:model.defer="polygonsOSMfrZ"/>
|
||||
</div>
|
||||
<div class="mt-4 font-mono text-sm" x-show="show">
|
||||
<p>
|
||||
X, Y, Z are parameters for the following PostGIS equation.
|
||||
The default values are chosen according to the size of the
|
||||
original geometry to give a slighty bigger geometry, without
|
||||
too many nodes.
|
||||
|
||||
</p>
|
||||
<p class="mt-4">Note that:</p>
|
||||
<p>
|
||||
X > 0 will give a polygon bigger than the original geometry,
|
||||
and guaranteed to contain it.
|
||||
</p>
|
||||
<p>
|
||||
X = 0 will give a polygon similar to the original geometry.
|
||||
</p>
|
||||
<p>
|
||||
X < 0 will give a polygon smaller than the original
|
||||
geometry, and guaranteed to be smaller. </p>
|
||||
</div>
|
||||
<div x-show="show" class="mt-2 font-semibold">
|
||||
<x-button emerald label="Submit and load polygons"
|
||||
wire:click="submitPolygonsOSM"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<div>
|
||||
@if (!$model?->simplified_geojson && $search)
|
||||
<x-badge lg positive class="xl:whitespace-nowrap">
|
||||
Now select the appropriate place so that a GeoJSON can be built.
|
||||
</x-badge>
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-y-auto px-4 py-5 lg:p-6">
|
||||
@if ($search)
|
||||
<h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-gray-100">
|
||||
Search: {{ $search }}
|
||||
</h3>
|
||||
@endif
|
||||
<div class="mt-2 text-sm text-gray-500">
|
||||
|
||||
<div class="flex max-h-[400px] flex-col space-y-4">
|
||||
<div class="mt-6 flow-root">
|
||||
<ul role="list" class="-my-5 divide-y divide-gray-200">
|
||||
|
||||
@foreach ($osmSearchResults as $item)
|
||||
@php
|
||||
$currentClass = $item['osm_id'] === $osm_id ? 'bg-amber-400 dark:bg-amber-900' : '';
|
||||
@endphp
|
||||
|
||||
<li class="{{ $currentClass }} cursor-pointer py-4 px-2 hover:bg-amber-400 dark:hover:bg-amber-800"
|
||||
wire:key="osmItem_{{ $loop->index }}"
|
||||
wire:click="selectItem({{ $loop->index }})">
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="min-w-0 flex-1">
|
||||
<p
|
||||
class="truncate text-sm font-medium text-gray-900 dark:text-gray-200">
|
||||
{{ $item['display_name'] }}</p>
|
||||
<p class="truncate text-sm text-gray-500">
|
||||
<x-badge amber>
|
||||
{{ count($item['geojson']['coordinates'], COUNT_RECURSIVE) }}
|
||||
points
|
||||
</x-badge>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<x-badge blue>{{ $item['type'] }}</x-badge>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Wikipedia Links --}}
|
||||
<div class="flex flex-row items-center space-x-6">
|
||||
@if ($search)
|
||||
<div class='rounded-lg bg-white px-4 py-5 shadow dark:bg-gray-800 lg:p-6'>
|
||||
<h1 class='font-semibold dark:text-gray-100'>Wikipedia search <span
|
||||
class='text-sm text-gray-500 dark:text-gray-400'>(for population data)</span></h1>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<a target="_blank" class="text-amber-500 underline"
|
||||
href="https://en.wikipedia.org/wiki/{{ urlencode(str($search)->replace(' ', '_')->toString()) }}">Wikipedia
|
||||
EN:
|
||||
{{ $search }}</a>
|
||||
<a target="_blank" class="text-amber-500 underline"
|
||||
href="https://de.wikipedia.org/wiki/{{ urlencode(str($search)->replace(' ', '_')->toString()) }}">Wikipedia
|
||||
DE:
|
||||
{{ $search }}</a>
|
||||
<a target="_blank" class="text-amber-500 underline"
|
||||
href="https://fr.wikipedia.org/wiki/{{ urlencode(str($search)->replace(' ', '_')->toString()) }}">Wikipedia
|
||||
FR:
|
||||
{{ $search }}</a>
|
||||
<a target="_blank" class="text-amber-500 underline"
|
||||
href="https://es.wikipedia.org/wiki/{{ urlencode(str($search)->replace(' ', '_')->toString()) }}">Wikipedia
|
||||
ES:
|
||||
{{ $search }}</a>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<div class='rounded-lg bg-white px-4 py-5 shadow dark:bg-gray-800 lg:p-6'>
|
||||
<x-input wire:model.debounce="population" label="population"/>
|
||||
<x-input wire:model.debounce="population_date" label="population_date"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- GeoJSON simplification --}}
|
||||
@if ($selectedItemOSMPolygons)
|
||||
<div class="rounded-lg bg-white shadow dark:bg-gray-800">
|
||||
<div class="px-4 py-5 lg:p-6">
|
||||
<div class="flex items-center space-x-4">
|
||||
<h3 class="text-lg font-medium leading-6 text-blue-500">
|
||||
Mapshaper simplification of <span class="text-[#FFA500]">OSM GeoJSON
|
||||
[{{ count($selectedItemOSMPolygons['geojson']['coordinates'], COUNT_RECURSIVE) }}
|
||||
points]</span> to
|
||||
{{ count($model->simplified_geojson['coordinates'], COUNT_RECURSIVE) }} points
|
||||
</h3>
|
||||
</div>
|
||||
<div class="mt-2 text-sm text-gray-500 dark:text-gray-200">
|
||||
<div class="flex flex-col space-y-2">
|
||||
<h1 class="py-2">
|
||||
(smaller percentage means fewer points - aim for no more than 150)
|
||||
</h1>
|
||||
<div class="flex hidden space-x-2 overflow-auto lg:block">
|
||||
@php
|
||||
$btnClassLeft = 'relative inline-flex items-center rounded-l-md border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 dark:bg-gray-600 dark:hover:bg-blue-800 hover:bg-blue-400 focus:z-10 focus:border-blue-500 dark:focus:border-blue-700 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:focus:ring-blue-700';
|
||||
$btnClassRight = 'relative -ml-px inline-flex items-center rounded-r-md border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 dark:bg-gray-600 dark:hover:bg-blue-800 hover:bg-blue-400 focus:z-10 focus:border-blue-500 dark:focus:border-blue-700 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:focus:ring-blue-700';
|
||||
$btnClassCenter = 'relative -ml-px inline-flex items-center border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 dark:bg-gray-600 dark:hover:bg-blue-800 hover:bg-blue-400 focus:z-10 focus:border-blue-500 dark:focus:border-blue-700 focus:outline-none focus:ring-1 focus:ring-blue-500 dark:focus:ring-blue-700';
|
||||
$currentClass = 'bg-blue-500 dark:bg-blue-700 text-white dark:text-gray-900';
|
||||
@endphp
|
||||
<div class="isolate inline-flex rounded-md shadow-sm">
|
||||
@foreach ($percentages as $percentage)
|
||||
@php
|
||||
$btnClass = $loop->first ? $btnClassLeft : ($loop->last ? $btnClassRight : $btnClassCenter);
|
||||
@endphp
|
||||
<button wire:key="percentage_{{ $loop->index }}" type="button"
|
||||
wire:click="setPercentage({{ $percentage }})"
|
||||
class="{{ $btnClass }} {{ $currentPercentage === $percentage ? $currentClass : '' }}">
|
||||
{{ $percentage }}%
|
||||
</button>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div class="block lg:hidden">
|
||||
<x-native-select label="Select percentage" placeholder="Select percentage"
|
||||
:options="$percentages" wire:model="currentPercentage"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- GeoJSON data --}}
|
||||
<div>
|
||||
@if ($model?->simplified_geojson && $selectedItemOSMPolygons)
|
||||
<div class="rounded-lg bg-white shadow dark:bg-gray-800">
|
||||
<div class="grid grid-cols-1 gap-4 px-4 py-5 lg:grid-cols-2 lg:p-6">
|
||||
<div>
|
||||
@php
|
||||
$jsonEncodedSelectedItem = json_encode($selectedItemOSMPolygons['geojson'], JSON_THROW_ON_ERROR);
|
||||
@endphp
|
||||
<h3 class="text-lg font-medium leading-6 text-[#FFA500]">
|
||||
OSM GeoJSON
|
||||
[{{ count($selectedItemOSMPolygons['geojson']['coordinates'], COUNT_RECURSIVE) }}
|
||||
points]
|
||||
</h3>
|
||||
<div class="mt-2 text-sm text-gray-500">
|
||||
<div class="flex w-full flex-col space-y-2">
|
||||
<pre
|
||||
class="overflow-x-auto py-3 text-[#FFA500]">{{ $jsonEncodedSelectedItem }}</pre>
|
||||
<div class='font-semibold'>
|
||||
<x-button x-data="{
|
||||
textToCopy: @entangle('selectedItemOSMPolygons.geojson')
|
||||
}"
|
||||
@click.prevent="window.navigator.clipboard.writeText(JSON.stringify(textToCopy));window.$wireui.notify({title:'{{ __('Copied!') }}',icon:'success'});"
|
||||
lg amber>
|
||||
Copy to clipboard
|
||||
</x-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
@php
|
||||
$jsonEncodedSimplifiedGeoJson = json_encode($model->simplified_geojson, JSON_THROW_ON_ERROR);
|
||||
@endphp
|
||||
<h3 class="text-lg font-medium leading-6 text-blue-500">
|
||||
Simplified GeoJSON
|
||||
[{{ count($model->simplified_geojson['coordinates'], COUNT_RECURSIVE) }} points]
|
||||
</h3>
|
||||
<div class="mt-2 text-sm text-gray-500">
|
||||
<div class="flex w-full flex-col space-y-2">
|
||||
<pre
|
||||
class="overflow-x-auto py-3 text-blue-500">{{ $jsonEncodedSimplifiedGeoJson }}</pre>
|
||||
<div class='font-semibold'>
|
||||
<x-button
|
||||
wire:click="saveSimplifiedGeoJson"
|
||||
lg blue>
|
||||
Save on model
|
||||
</x-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if ($selectedItemOSMBoundaries)
|
||||
<div>
|
||||
@php
|
||||
$jsonEncodedGeoJsonWater = json_encode($selectedItemOSMBoundaries, JSON_THROW_ON_ERROR);
|
||||
@endphp
|
||||
<h3 class="text-lg font-medium leading-6 text-[#FF0084]">
|
||||
https://osm-boundaries.com water GeoJSON
|
||||
[{{ count($selectedItemOSMBoundaries['coordinates'], COUNT_RECURSIVE) }}
|
||||
points]
|
||||
</h3>
|
||||
<div class="mt-2 text-sm text-gray-500">
|
||||
<div class="flex w-full flex-col space-y-2">
|
||||
<pre
|
||||
class="overflow-x-auto py-3 text-[#FF0084]">{{ $jsonEncodedGeoJsonWater }}</pre>
|
||||
<div class='font-semibold'>
|
||||
<x-button x-data="{
|
||||
textToCopy: @entangle('selectedItemOSMBoundaries')
|
||||
}"
|
||||
@click.prevent="window.navigator.clipboard.writeText(JSON.stringify(textToCopy));window.$wireui.notify({title:'{{ __('Copied!') }}',icon:'success'});"
|
||||
lg pink>
|
||||
Copy to clipboard
|
||||
</x-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@if ($selectedItemPolygonsOSMfr)
|
||||
<div>
|
||||
@php
|
||||
$jsonEncodedGeoJsonOSMFr = json_encode($selectedItemPolygonsOSMfr, JSON_THROW_ON_ERROR);
|
||||
@endphp
|
||||
<h3 class="text-lg font-medium leading-6 text-emerald-500">
|
||||
https://polygons.openstreetmap.fr GeoJSON
|
||||
<span wire:key="ifNotGeometryCollection">
|
||||
@if ($selectedItemPolygonsOSMfr['type'] !== 'GeometryCollection')
|
||||
[{{ count($selectedItemPolygonsOSMfr['coordinates'] ?? [], COUNT_RECURSIVE) }}
|
||||
points]
|
||||
@endif
|
||||
</span>
|
||||
</h3>
|
||||
<div class="mt-2 text-sm text-gray-500">
|
||||
<div class="flex w-full flex-col space-y-2">
|
||||
<pre
|
||||
class="overflow-x-auto py-3 text-emerald-500">{{ $jsonEncodedGeoJsonOSMFr }}</pre>
|
||||
<div class='font-semibold'>
|
||||
<x-button x-data="{
|
||||
textToCopy: @entangle('selectedItemPolygonsOSMfr')
|
||||
}"
|
||||
@click.prevent="window.navigator.clipboard.writeText(JSON.stringify(textToCopy));window.$wireui.notify({title:'{{ __('Copied!') }}',icon:'success'});"
|
||||
lg emerald>
|
||||
Copy to clipboard
|
||||
</x-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col space-y-4 px-4 py-5 lg:p-6">
|
||||
<div class="w-full">
|
||||
<div>
|
||||
<h1 class="font-bold dark:text-white">
|
||||
GeoJSON preview
|
||||
</h1>
|
||||
<div wire:ignore class="my-4" x-data="{
|
||||
geojson: @entangle('selectedItemOSMPolygons.geojson'),
|
||||
simplifiedGeojson: @entangle('model.simplified_geojson'),
|
||||
geojsonWater: @entangle('selectedItemOSMBoundaries'),
|
||||
geojsonOSMfr: @entangle('selectedItemPolygonsOSMfr'),
|
||||
init() {
|
||||
const map = L.map($refs.map)
|
||||
.setView([0, 0], 13);
|
||||
|
||||
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', { foo: 'bar', attribution: '© <a href=\'https://www.openstreetmap.org/copyright\'>OpenStreetMap</a> contributors' }).addTo(map);
|
||||
|
||||
const geojsonFeature = {
|
||||
'type': 'Feature',
|
||||
'geometry': this.geojson
|
||||
};
|
||||
const simplifiedGeojsonFeature = {
|
||||
'type': 'Feature',
|
||||
'geometry': this.simplifiedGeojson
|
||||
};
|
||||
L.geoJson(geojsonFeature, { style: { color: '#FFA500', fillColor: '#FFA500', fillOpacity: 0.3 } }).addTo(map);
|
||||
let simplifiedGeoJSON = L.geoJson(simplifiedGeojsonFeature, { style: { fillOpacity: 0.5 } }).addTo(map);
|
||||
map.fitBounds(simplifiedGeoJSON.getBounds(), { padding: [50, 50] });
|
||||
|
||||
$wire.on('geoJsonUpdated', () => {
|
||||
map.eachLayer((layer) => {
|
||||
layer.remove();
|
||||
});
|
||||
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', { foo: 'bar', attribution: '© <a href=\'https://www.openstreetmap.org/copyright\'>OpenStreetMap</a> contributors' }).addTo(map);
|
||||
const geojsonFeature = {
|
||||
'type': 'Feature',
|
||||
'geometry': this.geojson
|
||||
};
|
||||
const simplifiedGeojsonFeature = {
|
||||
'type': 'Feature',
|
||||
'geometry': this.simplifiedGeojson
|
||||
};
|
||||
const geojsonWaterFeature = {
|
||||
'type': 'Feature',
|
||||
'geometry': this.geojsonWater
|
||||
};
|
||||
const geojsonOSMfrFeature = {
|
||||
'type': 'Feature',
|
||||
'geometry': this.geojsonOSMfr
|
||||
};
|
||||
L.geoJson(geojsonFeature, { style: { color: '#FFA500', fillColor: '#FFA500', fillOpacity: 0.3 } }).addTo(map);
|
||||
L.geoJson(geojsonWaterFeature, { style: { color: '#FF0084', fillColor: '#FF0084', fillOpacity: 0.2 } }).addTo(map);
|
||||
L.geoJson(geojsonOSMfrFeature, { style: { color: '#10b981', fillColor: '#10b981', fillOpacity: 0.3 } }).addTo(map);
|
||||
let simplifiedGeoJSON = L.geoJson(simplifiedGeojsonFeature, { style: { fillOpacity: 0.5 } }).addTo(map);
|
||||
map.fitBounds(simplifiedGeoJSON.getBounds(), { padding: [50, 50] });
|
||||
});
|
||||
}
|
||||
}">
|
||||
<div x-ref="map" style="height: 50vh;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class='rounded-lg bg-white px-4 py-5 shadow dark:bg-gray-800 lg:p-6'>
|
||||
<p class="mt-8 text-xs leading-5 text-gray-400 md:order-1 md:mt-0">
|
||||
<a class="text-blue-500" href="https://github.com/HolgerHatGarKeineNode/geojson-helper"
|
||||
target="_blank">GeoJSON helper</a> is maintained by <a
|
||||
href="https://github.com/HolgerHatGarKeineNode" target="_blank"
|
||||
class="text-amber-500">HolgerHatGarKeineNode</a> [<span
|
||||
class="break-all font-mono">npub1pt0kw36ue3w2g4haxq3wgm6a2fhtptmzsjlc2j2vphtcgle72qesgpjyc6</span>].
|
||||
This
|
||||
software is open-sourced software
|
||||
licensed under the <a href="https://opensource.org/licenses/MIT" target="_blank"
|
||||
class="underline">MIT license</a>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
@if($meetup->city->osm_relation)
|
||||
<code>
|
||||
osm_id: {{ $meetup->city->osm_relation['osm_id'] }}
|
||||
</code>
|
||||
<code>
|
||||
display_name: {{ $meetup->city->osm_relation['display_name'] }}
|
||||
</code>
|
||||
@endif
|
||||
</div>
|
||||
<h1>Wikipedia Search Results</h1>
|
||||
<div class="flex space-x-2">
|
||||
<a target="_blank" class="underline text-amber-500"
|
||||
href="https://de.wikipedia.org/wiki/{{ urlencode($meetup->city->name) }}">Wikipedia: {{ $meetup->city->name }}</a>
|
||||
<x-input wire:model.debounce="population" label="population"/>
|
||||
<x-input wire:model.debounce="population_date" label="population_date"/>
|
||||
</div>
|
||||
<h1>DB population</h1>
|
||||
<code>
|
||||
population: {{ $meetup->city->population }}
|
||||
</code>
|
||||
<code>
|
||||
population date: {{ $meetup->city->population_date }}
|
||||
</code>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.leaflet-attribution-flag {
|
||||
display: inline;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user