├── resources ├── dist │ ├── .gitkeep │ └── filament-openstreetmap.css ├── views │ ├── .gitkeep │ └── forms │ │ └── components │ │ └── map.blade.php ├── lang │ └── en │ │ └── openstreetmap.php ├── js │ ├── types.d.ts │ └── index.ts └── css │ └── index.scss ├── bun.lockb ├── config └── filament-openstreetmap.php ├── src ├── FilamentOpenStreetMap.php ├── Testing │ └── TestsFilamentOpenStreetMap.php ├── Rules │ └── GeoPoint.php ├── FilamentOpenStreetMapServiceProvider.php └── Forms │ └── Components │ └── MapInput.php ├── postcss.config.cjs ├── CHANGELOG.md ├── LICENSE.md ├── bin └── build.js ├── composer.json └── README.md /resources/dist/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/views/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Traineratwot/filament-openstreetmap/HEAD/bun.lockb -------------------------------------------------------------------------------- /config/filament-openstreetmap.php: -------------------------------------------------------------------------------- 1 | 'en-US', 5 | ]; 6 | -------------------------------------------------------------------------------- /resources/lang/en/openstreetmap.php: -------------------------------------------------------------------------------- 1 | void): void 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/Testing/TestsFilamentOpenStreetMap.php: -------------------------------------------------------------------------------- 1 | 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /bin/build.js: -------------------------------------------------------------------------------- 1 | import esbuild from 'esbuild' 2 | 3 | const isDev = process.argv.includes('--dev') 4 | 5 | async function compile(options) { 6 | const context = await esbuild.context(options) 7 | 8 | if (isDev) { 9 | await context.watch() 10 | } else { 11 | await context.rebuild() 12 | await context.dispose() 13 | } 14 | } 15 | 16 | const defaultOptions = { 17 | define: { 18 | 'process.env.NODE_ENV':`'production'`, 19 | }, 20 | bundle: true, 21 | mainFields: ['module', 'main'], 22 | platform: 'neutral', 23 | sourcemap: true, 24 | sourcesContent: false, 25 | treeShaking: true, 26 | target: ['es2020'], 27 | minify: true, 28 | plugins: [{ 29 | name: 'watchPlugin', 30 | setup: function (build) { 31 | build.onStart(() => { 32 | console.log(`Build started at ${new Date(Date.now()).toLocaleTimeString()}: ${build.initialOptions.outfile}`) 33 | }) 34 | 35 | build.onEnd((result) => { 36 | if (result.errors.length > 0) { 37 | console.log(`Build failed at ${new Date(Date.now()).toLocaleTimeString()}: ${build.initialOptions.outfile}`, result.errors) 38 | } else { 39 | console.log(`Build finished at ${new Date(Date.now()).toLocaleTimeString()}: ${build.initialOptions.outfile}`) 40 | } 41 | }) 42 | } 43 | }], 44 | } 45 | 46 | compile({ 47 | ...defaultOptions, 48 | entryPoints: ['./resources/js/index.ts'], 49 | outfile: './resources/dist/filament-openstreetmap.js', 50 | }) 51 | -------------------------------------------------------------------------------- /src/Rules/GeoPoint.php: -------------------------------------------------------------------------------- 1 | $value->latitude, 25 | 'longitude' => $value->longitude, 26 | ]; 27 | } 28 | if (is_string($value)) { 29 | $_value = explode(',', $value); 30 | if (count($_value) !== 2) { 31 | $fail("The {$attribute} must be a valid geo point."); 32 | } 33 | 34 | $point = [ 35 | 'latitude' => (float) $_value[0], 36 | 'longitude' => (float) $_value[1], 37 | ]; 38 | } 39 | if (is_array($value)) { 40 | if (isset($value['type']) && $value['type'] === 'Point') { 41 | $point = [ 42 | 'latitude' => $value['coordinates'][1], 43 | 'longitude' => $value['coordinates'][0], 44 | ]; 45 | } elseif (count($value) !== 2) { 46 | $fail("The {$attribute} must be a valid geo point."); 47 | } else { 48 | $point = [ 49 | 'latitude' => (float) $value[0], 50 | 'longitude' => (float) $value[1], 51 | ]; 52 | } 53 | } 54 | if (! is_numeric($point['latitude']) || ! is_numeric($point['longitude'])) { 55 | $fail("The {$attribute} must be a valid geo point."); 56 | } 57 | } catch (Exception $e) { 58 | $fail("The {$attribute} must be a valid geo point."); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "traineratwot/filament-openstreetmap", 3 | "version": "1.2.2", 4 | "description": "This is my package filament-openstreetmap", 5 | "keywords": [ 6 | "Traineratwot", 7 | "laravel", 8 | "filament-openstreetmap" 9 | ], 10 | "homepage": "https://github.com/traineratwot/filament-openstreetmap", 11 | "support": { 12 | "issues": "https://github.com/traineratwot/filament-openstreetmap/issues", 13 | "source": "https://github.com/traineratwot/filament-openstreetmap" 14 | }, 15 | "license": "MIT", 16 | "authors": [ 17 | { 18 | "name": "Traineratwot", 19 | "email": "traineratwot@yandex.ru", 20 | "role": "Developer" 21 | } 22 | ], 23 | "require": { 24 | "php": "^8.1", 25 | "filament/filament": "^3", 26 | "filament/forms": "^3", 27 | "illuminate/contracts": "^10.0|^11.0", 28 | "matanyadaev/laravel-eloquent-spatial": "^3.0|^4.0", 29 | "spatie/laravel-package-tools": "^1.15.0" 30 | }, 31 | "require-dev": { 32 | "nunomaduro/collision": "^7.9|^8.0", 33 | "orchestra/testbench": "^8.0|^9.0", 34 | "pestphp/pest": "^2.0", 35 | "pestphp/pest-plugin-arch": "^2.0", 36 | "pestphp/pest-plugin-laravel": "^2.0" 37 | }, 38 | "autoload": { 39 | "psr-4": { 40 | "Traineratwot\\FilamentOpenStreetMap\\": "src/" 41 | } 42 | }, 43 | "autoload-dev": { 44 | "psr-4": { 45 | "Traineratwot\\FilamentOpenStreetMap\\Tests\\": "tests/" 46 | } 47 | }, 48 | "scripts": { 49 | "post-autoload-dump": "@php ./vendor/bin/testbench package:discover --ansi", 50 | "test": "vendor/bin/pest", 51 | "test-coverage": "vendor/bin/pest --coverage" 52 | }, 53 | "config": { 54 | "sort-packages": true, 55 | "allow-plugins": { 56 | "pestphp/pest-plugin": true, 57 | "phpstan/extension-installer": true 58 | } 59 | }, 60 | "extra": { 61 | "laravel": { 62 | "providers": [ 63 | "Traineratwot\\FilamentOpenStreetMap\\FilamentOpenStreetMapServiceProvider" 64 | ] 65 | } 66 | }, 67 | "minimum-stability": "dev", 68 | "prefer-stable": true 69 | } 70 | -------------------------------------------------------------------------------- /resources/js/index.ts: -------------------------------------------------------------------------------- 1 | import Map from 'ol/Map' 2 | import MousePosition from 'ol/control/MousePosition' 3 | import OSM from 'ol/source/OSM' 4 | import TileLayer from 'ol/layer/Tile' 5 | import View from 'ol/View' 6 | import { Coordinate, createStringXY } from 'ol/coordinate' 7 | import { defaults as defaultControls } from 'ol/control' 8 | import { Feature } from 'ol' 9 | import { fromLonLat, ProjectionLike } from 'ol/proj' 10 | import VectorSource from 'ol/source/Vector' 11 | import VectorLayer from 'ol/layer/Vector' 12 | import { Point } from 'ol/geom' 13 | import Geocoder from 'ol-geocoder' 14 | import { Icon, Style } from 'ol/style' 15 | 16 | window['traineratwot'] = {} 17 | 18 | 19 | class mPoint { 20 | constructor(public view: View, public projection: ProjectionLike) { 21 | } 22 | 23 | public onChange(callback: (lon: number, lat: number) => void) { 24 | try { 25 | this.view.on('change', () => { 26 | const [lat, lon] = this.getCoordinates() 27 | callback(lat, lon) 28 | }) 29 | } catch (e) { 30 | console.error(e) 31 | } 32 | } 33 | 34 | public getCoordinates() { 35 | return this.view.getCenter() 36 | } 37 | 38 | public setCoordinates(lat: number, lon: number) { 39 | this.view.setCenter(fromLonLat([lat, lon], this.projection)) 40 | } 41 | } 42 | 43 | function GetPointMap(id: string, lat: number = 0, lon: number = 0, zoom: number = 10, lang: string = 'en-US') { 44 | const projection = 'EPSG:4326' 45 | 46 | const mousePositionControl = new MousePosition({ 47 | coordinateFormat: createStringXY(4), 48 | projection: projection, 49 | className: `mouse-position-${id}`, 50 | target: document.getElementById(`OSMap-${id}`), 51 | }) 52 | let point = new Feature({ 53 | projection: projection, 54 | geometry: new Point(fromLonLat([lat, lon], projection)), 55 | }) 56 | const vectorSource = new VectorSource({ 57 | features: [point], 58 | }) 59 | const vectorLayer = new VectorLayer({ 60 | source: vectorSource, 61 | }) 62 | const MapLayer = new TileLayer({ 63 | source: new OSM(), 64 | }) 65 | const target = document.getElementById(`OSMap-${id}`) 66 | 67 | const view = new View({ 68 | projection: projection, 69 | center: fromLonLat([lat, lon], projection), 70 | zoom: zoom, 71 | }) 72 | const map = new Map({ 73 | controls: defaultControls().extend([mousePositionControl]), 74 | layers: [ 75 | MapLayer, 76 | vectorLayer, 77 | ], 78 | target: target, 79 | view: view, 80 | }) 81 | const geocoder = new Geocoder('nominatim', { 82 | provider: 'osm', 83 | lang: lang, //en-US, fr-FR 84 | placeholder: 'Поиск...', 85 | limit: 5, 86 | keepOpen: true, 87 | }) 88 | map.addControl(geocoder) 89 | try { 90 | geocoder.on('addresschosen', function(evt: any) { 91 | console.log(evt) 92 | const feature = evt.feature as Feature 93 | const coordinate = evt.coordinate as Coordinate 94 | feature.setStyle(new Style({ 95 | image: new Icon({ 96 | color: 'rgba(0, 0, 0, 0)', 97 | crossOrigin: 'anonymous', 98 | src: 'https://openlayers.org/en/latest/examples/data/dot.png', 99 | scale: 0.01, 100 | }), 101 | })) 102 | // application specific 103 | view.setCenter(fromLonLat([coordinate[0], coordinate[1]], projection)) 104 | }) 105 | } catch (e) { 106 | console.error(e) 107 | } 108 | 109 | function updateCenter() { 110 | // Получаем новые координаты центра карты 111 | const [lat, lon] = map.getView().getCenter() 112 | // Обновляем координаты точки 113 | point.getGeometry().setCoordinates([lat, lon]) 114 | } 115 | 116 | try { 117 | map.on('movestart', updateCenter) 118 | map.on('moveend', updateCenter) 119 | } catch (e) { 120 | console.warn(e) 121 | } 122 | target.classList.add('map-done') 123 | return new mPoint(view, projection) 124 | } 125 | 126 | window['traineratwot'].GetPointMap = GetPointMap 127 | -------------------------------------------------------------------------------- /src/FilamentOpenStreetMapServiceProvider.php: -------------------------------------------------------------------------------- 1 | name(static::$name) 33 | ->hasCommands($this->getCommands()) 34 | ->hasInstallCommand(function (InstallCommand $command) { 35 | $command 36 | ->publishConfigFile() 37 | ->publishMigrations() 38 | ->askToRunMigrations() 39 | ->askToStarRepoOnGitHub('traineratwot/filament-openstreetmap'); 40 | }); 41 | 42 | $configFileName = $package->shortName(); 43 | 44 | if (file_exists($package->basePath("/../config/{$configFileName}.php"))) { 45 | $package->hasConfigFile(); 46 | } 47 | 48 | if (file_exists($package->basePath('/../database/migrations'))) { 49 | $package->hasMigrations($this->getMigrations()); 50 | } 51 | 52 | if (file_exists($package->basePath('/../resources/lang'))) { 53 | $package->hasTranslations(); 54 | } 55 | 56 | if (file_exists($package->basePath('/../resources/views'))) { 57 | $package->hasViews(static::$viewNamespace); 58 | } 59 | } 60 | 61 | public function packageRegistered(): void 62 | { 63 | } 64 | 65 | /** 66 | * @throws ReflectionException 67 | */ 68 | public function packageBooted(): void 69 | { 70 | // Asset Registration 71 | FilamentAsset::register( 72 | $this->getAssets(), 73 | $this->getAssetPackageName() 74 | ); 75 | 76 | FilamentAsset::registerScriptData( 77 | $this->getScriptData(), 78 | $this->getAssetPackageName() 79 | ); 80 | 81 | // Icon Registration 82 | FilamentIcon::register($this->getIcons()); 83 | 84 | // Testing 85 | Testable::mixin(new TestsFilamentOpenStreetMap()); 86 | } 87 | 88 | protected function getAssetPackageName(): ?string 89 | { 90 | return 'traineratwot/filament-openstreetmap'; 91 | } 92 | 93 | /** 94 | * @return array 95 | */ 96 | protected function getAssets(): array 97 | { 98 | return [ 99 | Css::make('filament-openstreetmap-styles', __DIR__.'/../resources/dist/filament-openstreetmap.css'), 100 | Js::make('filament-openstreetmap-scripts', __DIR__.'/../resources/dist/filament-openstreetmap.js'), 101 | ]; 102 | } 103 | 104 | /** 105 | * @return array 106 | */ 107 | protected function getCommands(): array 108 | { 109 | return []; 110 | } 111 | 112 | /** 113 | * @return array 114 | */ 115 | protected function getIcons(): array 116 | { 117 | return []; 118 | } 119 | 120 | /** 121 | * @return array 122 | */ 123 | protected function getRoutes(): array 124 | { 125 | return []; 126 | } 127 | 128 | /** 129 | * @return array 130 | */ 131 | protected function getScriptData(): array 132 | { 133 | return []; 134 | } 135 | 136 | /** 137 | * @return array 138 | */ 139 | protected function getMigrations(): array 140 | { 141 | return [ 142 | 'create_filament-openstreetmap_table', 143 | ]; 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This is filament-openstreetmap 2 | 3 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/traineratwot/filament-openstreetmap.svg?style=flat-square)](https://packagist.org/packages/traineratwot/filament-openstreetmap) 4 | 5 | [![Total Downloads](https://img.shields.io/packagist/dt/traineratwot/filament-openstreetmap.svg?style=flat-square)](https://packagist.org/packages/traineratwot/filament-openstreetmap) 6 | 7 | 8 | 9 | **Add openstreetmap field to filament form** 10 | 11 | **Full free map API** 12 | 13 | ## Interface 14 | ![2024-01-19_09-54-03](https://github.com/Traineratwot/filament-openstreetmap/assets/41589091/fc0d847e-9d5a-4506-b445-d183b91f9198) 15 | ## How it view in database 16 | ![NVIDIA_Share_Yn8wCeCsJf](https://github.com/Traineratwot/filament-openstreetmap/assets/41589091/94c4a3f6-b75d-4fbc-87a1-cd02ffcde34a) 17 | 18 | ## Installation 19 | 20 | You can install the package via composer: 21 | 22 | ```bash 23 | composer require traineratwot/filament-openstreetmap 24 | ``` 25 | 26 | 27 | ## Usage 28 | 29 | Make model with migration 30 | 31 | 1) 32 | ```php 33 | 34 | return new class extends Migration { 35 | public function up(): void 36 | { 37 | Schema::create('map_points', function (Blueprint $table) { 38 | $table->id(); 39 | 40 | $table->point('point')->nullable(); // for Point type in Laravel 10 41 | $table->geography('point', 'point', 0)->nullable(); // for Point type in Laravel 11 42 | 43 | $table->string('point_string')->nullable(); // for String type 44 | $table->json('point_array')->nullable(); // for Array type 45 | $table->timestamps(); 46 | }); 47 | } 48 | 49 | public function down(): void 50 | { 51 | Schema::dropIfExists('map_points'); 52 | } 53 | }; 54 | ``` 55 | 2) 56 | 57 | ```php 58 | namespace App\Models; 59 | 60 | use MatanYadaev\EloquentSpatial\Objects\Point; 61 | use Illuminate\Database\Eloquent\Model; 62 | 63 | class MapPoint extends Model 64 | { 65 | 66 | protected $casts = [ 67 | 'point' => Point::class, // Important for Point type 68 | 'point_array' => 'array', // Important for Array type 69 | ]; 70 | 71 | ... 72 | } 73 | ``` 74 | Make filament resource 75 | 76 | ```php 77 | 78 | schema([ 93 | MapInput::make('point') 94 | ->saveAsPoint() // Important for Point type 95 | ->srid(4326) // Change srid for Point 96 | ->placeholder('Choose your location') 97 | ->coordinates(37.619, 55.7527) // start coordinates 98 | ->rows(10) // height of map 99 | , 100 | 101 | MapInput::make('point_string') 102 | ->saveAsString() // default 103 | ->placeholder('Choose your location') 104 | ->coordinates(37.619, 55.7527) // start coordinates 105 | ->rows(10) // height of map 106 | , 107 | 108 | MapInput::make('point_array') 109 | ->saveAsArray() // Important for Array type 110 | ->placeholder('Choose your location') 111 | ->coordinates(37.619, 55.7527) // start coordinates 112 | ->rows(10) // height of map 113 | , 114 | 115 | ]); 116 | } 117 | ... 118 | } 119 | 120 | 121 | ``` 122 | 123 | ## Changelog 124 | 125 | Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently. 126 | 127 | ## Contributing 128 | 129 | Please see [CONTRIBUTING](.github/CONTRIBUTING.md) for details. 130 | 131 | ## Security Vulnerabilities 132 | 133 | Please review [our security policy](../../security/policy) on how to report security vulnerabilities. 134 | 135 | ## Credits 136 | 137 | - [Traineratwot](https://github.com/Traineratwot) 138 | - [All Contributors](../../contributors) 139 | 140 | ## License 141 | 142 | The MIT License (MIT). Please see [License File](LICENSE.md) for more information. 143 | 144 | ## Used packages 145 | composer: matanyadaev/laravel-eloquent-spatial 146 | npm: ol 147 | npm: ol-geocoder 148 | -------------------------------------------------------------------------------- /src/Forms/Components/MapInput.php: -------------------------------------------------------------------------------- 1 | afterStateHydrated(static function (MapInput $component, $state): void { 47 | if (blank($state)) { 48 | return; 49 | } 50 | $value = $component->parseInput($state); 51 | $component->state("{$value['latitude']},{$value['longitude']}"); 52 | }); 53 | 54 | $this->dehydrateStateUsing(static function (MapInput $component, $state) { 55 | if (blank($state)) { 56 | return null; 57 | } 58 | $value = $component->parseInput($state); 59 | switch ($component->saveAs) { 60 | case 'Point': 61 | return new Point($value['latitude'], $value['longitude'], $component->srid); 62 | break; 63 | case 'Array': 64 | return [$value['latitude'], $value['longitude']]; 65 | break; 66 | case 'String': 67 | default: 68 | return "{$value['latitude']},{$value['longitude']}"; 69 | break; 70 | } 71 | }); 72 | 73 | $this->rules([new GeoPoint()]); 74 | } 75 | 76 | public function saveAsPoint(): static 77 | { 78 | $this->saveAs = 'Point'; 79 | 80 | return $this; 81 | } 82 | 83 | public function saveAsString(): static 84 | { 85 | $this->saveAs = 'String'; 86 | 87 | return $this; 88 | } 89 | 90 | public function saveAsArray(): static 91 | { 92 | $this->saveAs = 'Array'; 93 | 94 | return $this; 95 | } 96 | 97 | public function srid(int $srid): static 98 | { 99 | $this->srid = $srid; 100 | 101 | return $this; 102 | } 103 | 104 | protected function parseInput(mixed $state): array 105 | { 106 | $validator = Validator::make([ 107 | 'state' => $state, 108 | ], [ 109 | 'state' => ['required', new GeoPoint()], 110 | ]); 111 | if ($validator->fails()) { 112 | return [ 113 | 'latitude' => 0, 114 | 'longitude' => 0, 115 | ]; 116 | } 117 | if ($state instanceof Point) { 118 | return [ 119 | 'latitude' => $state->latitude, 120 | 'longitude' => $state->longitude, 121 | ]; 122 | } 123 | if (is_array($state)) { 124 | if (isset($state['type']) && $state['type'] === 'Point') { 125 | return [ 126 | 'latitude' => $state['coordinates'][1], 127 | 'longitude' => $state['coordinates'][0], 128 | ]; 129 | } 130 | 131 | return [ 132 | 'latitude' => (float) $state[0], 133 | 'longitude' => (float) $state[1], 134 | ]; 135 | } 136 | 137 | if (is_string($state)) { 138 | $_state = explode(',', $state); 139 | 140 | return [ 141 | 'latitude' => (float) $_state[0], 142 | 'longitude' => (float) $_state[1], 143 | ]; 144 | } 145 | 146 | return [ 147 | 'latitude' => $this->latitude, 148 | 'longitude' => $this->longitude, 149 | ]; 150 | } 151 | 152 | /** 153 | * @param float|int|Closure $zoom 1-20 (default 10) 154 | * @return $this 155 | */ 156 | public function zoom(float|int|Closure $zoom): static 157 | { 158 | $this->zoom = $zoom; 159 | 160 | return $this; 161 | } 162 | 163 | /** 164 | * @return $this 165 | */ 166 | public function latitude(float|int|Closure $latitude): static 167 | { 168 | $this->latitude = $latitude; 169 | 170 | return $this; 171 | } 172 | 173 | /** 174 | * @return $this 175 | */ 176 | public function longitude(float|int|Closure $longitude): static 177 | { 178 | $this->longitude = $longitude; 179 | 180 | return $this; 181 | } 182 | 183 | /** 184 | * @return $this 185 | */ 186 | public function coordinates(float|int|Closure $latitude, float|int|Closure $longitude): static 187 | { 188 | $this->latitude = $latitude; 189 | $this->longitude = $longitude; 190 | 191 | return $this; 192 | } 193 | 194 | public function getLatitude(): ?float 195 | { 196 | $a = $this->parseInput($this->getState()); 197 | 198 | return $a['latitude'] ?: $this->evaluate($this->latitude) ?: 0; 199 | } 200 | 201 | public function getLongitude(): ?float 202 | { 203 | $a = $this->parseInput($this->getState()); 204 | 205 | return $a['longitude'] ?: $this->evaluate($this->longitude) ?: 0; 206 | } 207 | 208 | public function getZoom(): ?float 209 | { 210 | return $this->evaluate($this->zoom) ?: 10; 211 | } 212 | 213 | public function geoCoderLang($lang = null): static 214 | { 215 | $this->geoCoderLang = $lang ?? config('filament-openstreetmap.locale'); 216 | 217 | return $this; 218 | } 219 | 220 | public function getGeoCoderLang(): string 221 | { 222 | return $this->geoCoderLang ?? config('filament-openstreetmap.locale') ?? 'en-US'; 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /resources/views/forms/components/map.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | use Filament\Support\Facades\FilamentView; 3 | 4 | $hasInlineLabel = $hasInlineLabel(); 5 | $isConcealed = $isConcealed(); 6 | $isDisabled = $isDisabled(); 7 | $rows = $getRows(); 8 | $shouldAutosize = $shouldAutosize(); 9 | $statePath = $getStatePath(); 10 | $startLat = $getLatitude(); 11 | $startLon = $getLongitude(); 12 | $geoCoderLang = $getGeoCoderLang(); 13 | $zoom = $getZoom(); 14 | $initialHeight = (($rows ?? 2) * 1.5) + 0.75; 15 | @endphp 16 | 21 | 26 | $hasInlineLabel, 30 | ]) 31 | > 32 | {{ $getLabel() }} 33 | 34 | 35 | 44 | 45 |
46 |
47 |
48 | 49 | 75 |
76 | 84 | 99 | 100 | 106 | 107 | 108 | 168 |
169 | -------------------------------------------------------------------------------- /resources/dist/filament-openstreetmap.css: -------------------------------------------------------------------------------- 1 | :host,:root{--ol-background-color:#fff;--ol-accent-background-color:#f5f5f5;--ol-subtle-background-color:#80808040;--ol-partial-background-color:#ffffffbf;--ol-foreground-color:#333;--ol-subtle-foreground-color:#666;--ol-brand-color:#0af}.ol-box{box-sizing:border-box;border-radius:2px;border:1.5px solid var(--ol-background-color);background-color:var(--ol-partial-background-color)}.ol-mouse-position{top:8px;right:8px;position:absolute}.ol-scale-line{background:var(--ol-partial-background-color);border-radius:4px;bottom:8px;left:8px;padding:2px;position:absolute}.ol-scale-line-inner{border:1px solid var(--ol-subtle-foreground-color);border-top:none;color:var(--ol-foreground-color);font-size:10px;text-align:center;margin:1px;will-change:contents,width;transition:all .25s}.ol-scale-bar{position:absolute;bottom:8px;left:8px}.ol-scale-bar-inner{display:flex}.ol-scale-step-marker{width:1px;height:15px;background-color:var(--ol-foreground-color);float:right;z-index:10}.ol-scale-step-text{bottom:-5px;font-size:10px;z-index:11}.ol-scale-step-text,.ol-scale-text{position:absolute;color:var(--ol-foreground-color);text-shadow:-1.5px 0 var(--ol-partial-background-color),0 1.5px var(--ol-partial-background-color),1.5px 0 var(--ol-partial-background-color),0 -1.5px var(--ol-partial-background-color)}.ol-scale-text{font-size:12px;text-align:center;bottom:25px}.ol-scale-singlebar{position:relative;height:10px;z-index:9;box-sizing:border-box;border:1px solid var(--ol-foreground-color)}.ol-scale-singlebar-even{background-color:var(--ol-subtle-foreground-color)}.ol-scale-singlebar-odd{background-color:var(--ol-background-color)}.ol-unsupported{display:none}.ol-unselectable,.ol-viewport{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}.ol-viewport canvas{all:unset;overflow:hidden}.ol-viewport{touch-action:pan-x pan-y}.ol-selectable{-webkit-touch-callout:default;-webkit-user-select:text;-moz-user-select:text;user-select:text}.ol-grabbing{cursor:grabbing}.ol-grab{cursor:move;cursor:grab}.ol-control{position:absolute;background-color:var(--ol-subtle-background-color);border-radius:4px}.ol-zoom{top:.5em;left:.5em}.ol-rotate{top:.5em;right:.5em;transition:opacity .25s linear,visibility 0s linear}.ol-rotate.ol-hidden{opacity:0;visibility:hidden;transition:opacity .25s linear,visibility 0s linear .25s}.ol-zoom-extent{top:4.643em;left:.5em}.ol-full-screen{right:.5em;top:.5em}.ol-control button{display:block;margin:1px;padding:0;color:var(--ol-subtle-foreground-color);font-weight:700;text-decoration:none;font-size:inherit;text-align:center;height:1.375em;width:1.375em;line-height:.4em;background-color:var(--ol-background-color);border:none;border-radius:2px}.ol-control button::-moz-focus-inner{border:none;padding:0}.ol-zoom-extent button{line-height:1.4em}.ol-compass{display:block;font-weight:400;will-change:transform}.ol-touch .ol-control button{font-size:1.5em}.ol-touch .ol-zoom-extent{top:5.5em}.ol-control button:focus,.ol-control button:hover{text-decoration:none;outline:1px solid var(--ol-subtle-foreground-color);color:var(--ol-foreground-color)}.ol-zoom .ol-zoom-in{border-radius:2px 2px 0 0}.ol-zoom .ol-zoom-out{border-radius:0 0 2px 2px}.ol-attribution{text-align:right;bottom:.5em;right:.5em;max-width:calc(100% - 1.3em);display:flex;flex-flow:row-reverse;align-items:center}.ol-attribution a{color:var(--ol-subtle-foreground-color);text-decoration:none}.ol-attribution ul{margin:0;padding:1px .5em;color:var(--ol-foreground-color);text-shadow:0 0 2px var(--ol-background-color);font-size:12px}.ol-attribution li{display:inline;list-style:none}.ol-attribution li:not(:last-child):after{content:" "}.ol-attribution img{max-height:2em;max-width:inherit;vertical-align:middle}.ol-attribution button{flex-shrink:0}.ol-attribution.ol-collapsed ul{display:none}.ol-attribution:not(.ol-collapsed){background:var(--ol-partial-background-color)}.ol-attribution.ol-uncollapsible{bottom:0;right:0;border-radius:4px 0 0}.ol-attribution.ol-uncollapsible img{margin-top:-.2em;max-height:1.6em}.ol-attribution.ol-uncollapsible button{display:none}.ol-zoomslider{top:4.5em;left:.5em;height:200px}.ol-zoomslider button{position:relative;height:10px}.ol-touch .ol-zoomslider{top:5.5em}.ol-overviewmap{left:.5em;bottom:.5em}.ol-overviewmap.ol-uncollapsible{bottom:0;left:0;border-radius:0 4px 0 0}.ol-overviewmap .ol-overviewmap-map,.ol-overviewmap button{display:block}.ol-overviewmap .ol-overviewmap-map{border:1px solid var(--ol-subtle-foreground-color);height:150px;width:150px}.ol-overviewmap:not(.ol-collapsed) button{bottom:0;left:0;position:absolute}.ol-overviewmap.ol-collapsed .ol-overviewmap-map,.ol-overviewmap.ol-uncollapsible button{display:none}.ol-overviewmap:not(.ol-collapsed){background:var(--ol-subtle-background-color)}.ol-overviewmap-box{border:1.5px dotted var(--ol-subtle-foreground-color)}.ol-overviewmap .ol-overviewmap-box:hover{cursor:move}/*! 2 | * ol-geocoder - v4.3.1 3 | * A geocoder extension compatible with OpenLayers v6.x, v7.x & v8.x 4 | * https://github.com/Dominique92/ol-geocoder 5 | * Built: 15/09/2023 16:57:43 6 | */.ol-touch .ol-control.gcd-gl-control button{font-size:1.14em}.ol-touch .ol-geocoder.gcd-gl-container{font-size:1.1em}.ol-geocoder.gcd-gl-container{box-sizing:border-box;font-size:.9em;left:.5em;position:absolute;top:4.875em}.ol-geocoder.gcd-gl-container *,.ol-geocoder.gcd-gl-container :after,.ol-geocoder.gcd-gl-container :before{box-sizing:inherit}.ol-geocoder .gcd-gl-control{height:2.1875em;overflow:hidden;transition:width .2s,height .2s;width:2.1875em}.ol-geocoder .gcd-gl-expanded{height:2.1875em;width:15.625em}.ol-geocoder .gcd-gl-input{background-color:#fff;border:1px solid #ccc;color:#222;font-family:inherit;font-size:.875em;left:2.5em;padding:5px;position:absolute;top:.25em;width:14.84375em;z-index:99}.ol-geocoder .gcd-gl-input:focus{border:none;box-shadow:inset 0 0 0 1px #4d90fe,inset 0 0 5px #4d90fe;outline:none}.ol-geocoder .gcd-gl-search{background-color:initial;border:none;cursor:pointer;display:inline-block;height:100%;line-height:1.4;outline:0;position:absolute;right:0;top:0;width:1.5625em;z-index:100}.ol-geocoder .gcd-gl-search:after{color:#333;content:"\2386";cursor:pointer;display:inline-block;font-size:1.5em}.ol-geocoder .gcd-gl-btn{cursor:pointer;height:1.5625em;left:.125em;position:absolute;top:.125em;width:1.5625em}.ol-geocoder .gcd-gl-btn:after{content:"\1F50D"}.ol-geocoder ul.gcd-gl-result{background-color:#fff;border-radius:4px;border-top:none;border-top-left-radius:0;border-top-right-radius:0;box-shadow:0 1px 7px #000c;left:2em;list-style:none;margin:0;max-height:18.75em;overflow-x:hidden;overflow-y:auto;padding:0;position:absolute;top:2.1875em;transition:max-height .3s ease-in;white-space:normal;width:16.25em}.ol-geocoder ul.gcd-gl-result>li{border-bottom:1px solid #eee;line-height:.875rem;overflow:hidden;padding:0;width:100%}.ol-geocoder ul.gcd-gl-result>li>a{display:block;padding:3px 5px;text-decoration:none}.ol-geocoder ul.gcd-gl-result>li>a:hover{background-color:#d4d4d4}.ol-geocoder ul.gcd-gl-result>li:nth-child(odd){background-color:#e0ffe0}.ol-geocoder ul.gcd-gl-result:empty{display:none}.ol-geocoder.gcd-txt-container{box-sizing:border-box;height:4.375em;left:calc(50% - 12.5em);position:absolute;top:.5em;width:25em}.ol-geocoder.gcd-txt-container *,.ol-geocoder.gcd-txt-container :after,.ol-geocoder.gcd-txt-container :before{box-sizing:inherit}.ol-geocoder .gcd-txt-control{background-color:#fff;border:1px solid #ccc;height:4.375em;overflow:hidden;position:relative;width:100%}.ol-geocoder .gcd-txt-label{display:inline-block;text-align:center;width:100%}.ol-geocoder .gcd-txt-input{background-color:initial;border:none;font-family:inherit;font-size:.875em;height:100%;left:0;padding:5px 30px 5px 40px;position:absolute;text-indent:6px;top:0;width:100%;z-index:99}.ol-geocoder .gcd-txt-input:focus{box-shadow:inset 0 0 0 1px #4d90fe,inset 0 0 6px #4d90fe;outline:none}.ol-geocoder .gcd-txt-search{background-color:initial;border:none;cursor:pointer;display:inline-block;height:100%;line-height:100%;outline:0;position:absolute;right:0;top:0;vertical-align:middle;width:2.5em;z-index:100}.ol-geocoder .gcd-txt-search:after{color:#333;content:"\2386";cursor:pointer;display:inline-block;font-size:2em}.ol-geocoder .gcd-txt-glass{display:inline-block;height:100%;left:9px;position:absolute;top:26px;width:2.5em;z-index:100}.ol-geocoder .gcd-txt-glass:after{content:"\1F50D"}.ol-geocoder ul.gcd-txt-result{background-color:#fff;border-radius:4px;border-top:none;border-top-left-radius:0;border-top-right-radius:0;box-shadow:0 1px 7px #000c;left:0;list-style:none;margin:0;max-height:18.75em;overflow-x:hidden;overflow-y:auto;padding:0;position:absolute;top:4.575em;transition:max-height .3s ease-in;white-space:normal;width:25em}.ol-geocoder ul.gcd-txt-result>li{border-bottom:1px solid #eee;line-height:.875rem;overflow:hidden;padding:0;width:100%}.ol-geocoder ul.gcd-txt-result>li>a{display:block;padding:3px 5px;text-decoration:none}.ol-geocoder ul.gcd-txt-result>li>a:hover{background-color:#d4d4d4}.ol-geocoder ul.gcd-txt-result>li:nth-child(odd){background-color:#e0ffe0}.ol-geocoder ul.gcd-txt-result:empty{display:none}.ol-geocoder .gcd-hidden{opacity:0;visibility:hidden}.ol-geocoder .gcd-pseudo-rotate:after{animation:spin .7s linear infinite}.gcd-address,.gcd-road{color:#333;font-size:.875em;font-weight:500}.gcd-city{font-weight:400}.gcd-city,.gcd-country{color:#333;font-size:.75em}.gcd-country{font-weight:lighter}.open-street-map{position:relative}.open-street-map .gcd-txt-control{color:#09090b!important}.open-street-map.disabled,.open-street-map.disabled .gcd-gl-control{pointer-events:none}.open-street-map .center{z-index:9999!important;position:absolute;border:1px solid #000;top:50%;left:50%;transform:translate(-50%,-50%);width:20px;height:20px} --------------------------------------------------------------------------------