Rails 7 importmap leaflet-css images path fix? showed how to get leaflet working in a Stimulus controller.
import { Controller } from "#hotwired/stimulus";
import "leaflet-css";
export default class extends Controller {
static targets = [ "trial" ]
connect(){
import("leaflet").then( L => {
this.map = L.map(this.trialTarget).setView([ 51.472814, 7.321673 ], 14);
var base_map = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap | SwmmGo',
transparency: true,
opacity: 0.5
}).addTo(this.map);
Can't figure out how to do this with OpenLayers. import("leaflet").then( L => { mapping is probably the key, but I don't understand what this is doing.
Beginning of controller:
import { Controller } from "#hotwired/stimulus";
export default class extends Controller {
static targets = [ "map" ]
connect(){
import("openlayers").then( ol => {
import GeoJSON from 'ol/format/GeoJSON';
etc.
Error is: controller: street (controllers/street_controller) SyntaxError: Unexpected identifier 'GeoJSON'
# config/importmaps.rb
pin "application", preload: true
pin "#hotwired/turbo-rails", to: "turbo.min.js", preload: true
pin "#hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "#hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true # Rail7 default also
pin_all_from "app/javascript/controllers", under: "controllers"
pin "jquery", to: "https://ga.jspm.io/npm:jquery#3.6.1/dist/jquery.js", preload: true
pin "popper", to: 'popper.js', preload: true
pin "bootstrap", to: 'bootstrap.min.js', preload: true
pin "leaflet", to: "https://ga.jspm.io/npm:leaflet#1.9.2/dist/leaflet-src.js", preload: true
pin "leaflet-css", to: "https://ga.jspm.io/npm:leaflet-css#0.1.0/dist/leaflet.css.min.js", preload: true
pin "leaflet.timeline", to: "https://ga.jspm.io/npm:leaflet.timeline#1.4.3/dist/index.js", preload: true
pin "openlayers", to: "https://ga.jspm.io/npm:openlayers#4.6.5/dist/ol.js"
This is a partial answer in that I changed to esbuild since it aligned better with node_modules and I could leverage some suggestions I saw for frameworks other than Rails. I made a simple controller to sort out the problems.
// app/javascript/controllers/ol_map_test_controller.js
import { Controller } from "#hotwired/stimulus"
import ol from 'openlayers/dist/ol.js'
// import Map from 'ol/Map' // does not work. Errors in compilation
export default class extends Controller {
static targets = [ "test" ] // is for Stimulus
connect() {
this.map = new ol.Map({
// this.map = new Map({
target: 'map', // #map defined separately from Stimulus target
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
],
view: new ol.View({
center: [0, 0],
zoom: 2
})
});
}
}
and the HTML
<div id="map" data-controller="ol-map-test" data-ol-map-test-target="test" style="height:200px"></div>
Two things had to be done. Abandon defining Map from 'ol/Map' and associated wizardry of OpenLayers, i.e., not 'new Map'. One hopes that will get sorted out.
Secondly the div id has to be explicitly defined in HTML since Stimulus doesn't add that. The two targets could both be named map but for my sanity I named them differently.
Unfortunately this doesn't solve my problem as an OpenLayers module I need has the modern wizardry and doesn't compile the js.
I may see if this works with importmaps, but wanted to post this while I remembered what I had done.
One advantage to Stimulus is that one can easily experiment with other sample controllers with only one line of HTML.
Related
I pretty new with Drupal and I am writing a custom map module.
My module work fine, I can dispay my map and informations but I wrote the calling of my geojson with an hard path and I didn't find in the documentation how to call with relative/dynamic path my file to make possible to deploy my module on other website.
I think I must use route but I don't understand how that could work.
So here my code :
my javascript (carto_annuaire.js), this is the first line my real problem
var url = "/drupal-site/recommended-project/web/modules/custom/carto_conseillers/src/geojson/annuaire_cf.geojson";
var centerProj = new ol.proj.transform([1.33333,47.583328], 'EPSG:4326', 'EPSG:3857');
const source = new ol.source.Vector({
url: url,
format: new ol.format.GeoJSON(),
});
const view = new ol.View({
center: centerProj,
zoom: 8
});
const vectorLayer = new ol.layer.Vector({
source: source,
style: function (feature) {
const color = feature.get('color') || '#c7c7c7';
style.getFill().setColor(color);
return style;
},
});
My module.librairies.yml
carto_annuaire:
js:
js/carto_annuaire.js: {}
dependencies:
# - core/drupal
css:
theme:
css/carto_annuaire.css: {}
openlayer:
remote: https://github.com/openlayers/openlayers/
version: 7.2.2
license:
name: BSD 2-Clause License
url: https://github.com/openlayers/openlayers/blob/main/LICENSE.md
gpl-compatible: true
js:
https://cdn.jsdelivr.net/npm/ol#v7.2.2/dist/ol.js: { type: external }
my module.routing.yml
carto_conseillers.content:
path: '/carto_conseillers'
defaults:
_controller: '\Drupal\carto_conseillers\Controller\CartoConseillersController::content'
_title: 'Une page exemple avec un contenu simple'
requirements:
_permission: 'access content'
core: 8.x
and my ModuleController.php
namespace Drupal\carto_conseillers\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Component\Utility\Html;
class CartoConseillersController {
public function content() {
return array(
'#type' => 'markup',
'#markup' => t('<div id="map" class="map"></div>
<div id="info"> </div>
<div id="popup"></div>'),
'#attached' => array(
'library' => array(
'carto_conseillers/openlayer',
'carto_conseillers/carto_annuaire',
)
),
);
}
}
Any clue is welcome.
Thanks for your time.
Regards
I find out how to resolve as it was explain there :
Attaching configurable JavaScript
In the controller just add this line :
$block['#attached']['drupalSettings']['myVar'] = 'myPath';
and in the javascript you can access to the value via
var url = drupalSettings.myVar;
I am trying to figure out how I can access the ArCGIS JS API from a map after the map has been rendered, outside of require (ArcGIS JS API uses Dojo). For example, so I can do stuff like add (or remove) points, and perform other operations on the map.
I can create a map as follows:
require(["esri/config", "esri/Map", "esri/views/MapView", "esri/Graphic",
"esri/layers/GraphicsLayer"
], function(esriConfig, Map, MapView, Graphic, GraphicsLayer) {
esriConfig.apiKey = "";
const map = new Map({
basemap: "arcgis-topographic"
});
const view = new MapView({
map: map,
center: [-81, 41],
zoom: 9,
container: "viewDiv"
});
});
And I can add a point using this function:
function plotPoint(lat, long, props) {
const popupTemplate = {
title: "{Name}",
content: "{Description}"
}
const attributes = {
Name: props.name,
Description: props.desc
}
const graphicsLayer = new GraphicsLayer();
map.add(graphicsLayer);
const point = {
type: "point",
longitude: long,
latitude: lat
};
const simpleMarkerSymbol = {
type: "simple-marker",
color: [226, 119, 40],
outline: {
color: [255, 255, 255],
width: 1
}
};
const pointGraphic = new Graphic({
geometry: point,
symbol: simpleMarkerSymbol,
attributes: attributes,
popupTemplate: popupTemplate
});
graphicsLayer.add(pointGraphic);
}
But plotPoint needs to be within the require callback so it can access the referenced modules (like GraphicsLayer). I could assign it to the global window object so I could call it outside of require, but then I may run into an issue where the function is called before it's defined.
I may need to perform other operations too from other points in the code, like removing points, adding feature layers, etc. Unfortunately, this must all exist inside some legacy code, so I can't refactor the entire application.
Is there a better pattern for accessing the API outside of require?
I think that the easy way to achieve what you want, if I understand you correctly, is just to define modules and include it in you application.
A simple example base on you code would be something like this,
GraphicsManager.js
define([
"dojo/_base/declare",
"esri/Graphic",
"esri/layers/GraphicsLayer"
], function(declare, Graphic, GraphicsLayer){
return declare(null, {
plotPoint: function(lat, long, props){
// .. here the logic
return graphicsLayer;
}
});
});
main.js
require(["esri/config", "esri/Map", "esri/views/MapView", "app/GraphicsManager"
], function(esriConfig, Map, MapView, GraphicsManager) {
esriConfig.apiKey = "";
const map = new Map({
basemap: "arcgis-topographic",
});
const view = new MapView({
map: map,
center: [-81, 41],
zoom: 9,
container: "viewDiv"
});
// ... some logic to get the point data
const gm = new GraphicsManager();
map.add(gm.plotPoint(lat, long, props));
// .. some other logic
});
There you see that the main.js is where the application start, things are set there or in others modules. You know, map, layers, widgets, etc.
Then you have your other code in modules, and you use import them as required.
dojotoolkit - intro to modules
I am using VueMapbox (0.4.1) to utilize Mapbox GL in a Vue project.
<template>
<MglMap
:accessToken="accessToken"
:mapStyle.sync="mapStyle"
:repaint="true"
#load="onMapLoaded">
<MglMarker
:coordinates="someCoordinates"
class="map-marker-wrapper">
<div
slot="marker"
class="map-marker">
</div>
</MglMarker>
</MglMap>
</template>
<script>
import Mapbox from 'mapbox-gl'
import { MglMap, MglMarker } from 'vue-mapbox'
import * as MAP from '#/constants/map'
// Vue-Mapbox documentation: https://soal.github.io/vue-mapbox/guide/basemap.html#adding-map-component
export default {
name: 'Map',
components: {
MglMap,
MglMarker
},
props: {
someCoordinates: {
type: Array,
required: true
},
darkMode: {
type: Boolean,
required: true
}
},
data () {
return {
accessToken: MAP.ACCESS_TOKEN,
mapbox: null,
map: null,
actionsDispatcher: null
}
},
computed: {
mapStyle () {
return this.darkMode ? MAP.COLOR_PROFILES.DARK : MAP.COLOR_PROFILES.LIGHT
}
},
created () {
this.mapbox = Mapbox
},
methods: {
async onMapLoaded (event) {
this.map = event.map
this.actionsDispatcher = event.component.actions
await this.actionsDispatcher.flyTo({
center: this.someCoordinates
})
}
}
}
</script>
On the first load, everything works as expected:
But if I move to a different pseudo-route (say from /#/Map to /#/Profile and back), some of the map layers specified by my mapStyle (roads, city names,..) are not rendered anymore (default layers instead). The map also stops honoring any change of the mapStyle url, even when I specify mapStyle.sync in my template.
If I hit the browser's reload button it loads the layers as expected as the entire app is reloaded from scratch, but I unfortunately cannot afford to do this by default.
Any ideas are greatly appreciated.
I found a solution, in the example of vue-mapbox, the variable map (this.map) is set by "event.map" which causes an error because the card is not refreshed afterwards.
In my case i just remove that (this.map = event.map) in my onMapLoaded function and this is great.
Have a good day.
Despite I don’t know the syntax of Vue.js, the problem you are facing is that you are creating your layers in map.on('load', ... which is an event that happens only once, so when the style change happens, all the layers of the map style (including the ones created by custom code) are removed.
If you want to recreate your layers on style change, you have to do it in the event map.on('style.load', ..., but as said, I don’t see in your vue.js code where that is being done. If you share the part of the code where vue.js is invoking the methods it’ll be easier to help you
I'm using leaflet to show some markers on map on my angular app.
When I first load the page the map shows ok, the problem is when I navigate to another page (with ui.router) and get back to the map page the tiles does not render anymore (all tiles are gray).
var map;
var all_coordinates;
function createMap(centrals) {
// create the tile layer with correct attribution
// set up the map
map = new L.Map('map', {
scrollWheelZoom: false
});
var baseMaps = {
"Real Image": esri,
"Roads": roads
};
roads.addTo(map);
all_coordinates = SOME_COORDINATES;
var overlayMaps = {"Name": SOME_LAYERS};
L.control.layers(baseMaps, overlayMaps).addTo(map);
//allCoordinates.push(L.latLng(37.016085, -7.933859));
map.fitBounds(all_coordinates).addLayer(centrals_layers);
return map;
}
Anyone has an ideia on what am I doing wrong?
EDIT:
Here is the route config for the map page:
$stateProvider
.state('home', {
url: "/home",
templateUrl: "/static/app/views/dashboard.html",
controller: "homeController",
resolve: {
loadPlugin: function ($ocLazyLoad) {
return $ocLazyLoad.load([
{
serie: true,
files: ['/static/vendor/datatables.net/js/jquery.dataTables.min.js',
'/static/vendor/datatables.net-bs/css/dataTables.bootstrap.min.css',
'/static/vendor/datatables.net-bs/js/dataTables.bootstrap.min.js']
},
{
serie: true,
name: 'datatables',
files: ['/static/vendor/angular-datatables/dist/angular-datatables.min.js',]
},
{
serie: true,
name: 'datatables.buttons',
files: ['/static/vendor/datatables.net-buttons/js/dataTables.buttons.min.js',
'/static/vendor/datatables.net-buttons-bs/css/buttons.bootstrap.min.css',
'/static/vendor/datatables.net-buttons-bs/js/buttons.bootstrap.min.js',
'/static/vendor/angular-datatables/dist/plugins/buttons/angular-datatables.buttons.min.js']
}
]);
}
}
})
Same problem as in Leaflet map loading half greyed tiles and related questions (e.g. Leaflet Map not showing in bootstrap div, Leaflet map not displayed properly inside tabbed panel, leaflet map shows up grey, etc etc) - just run map.invalidateSize() when your page layout is stable.
It looks like there is a weird bug when trying to display an ol3 map within a modal. The map is in the modal but it doesn't display. Resizing the window manually forces it display however. Here's a link to try and see what I mean. Click on the settings pulldown within each map. Click on 'Get Feature Info'. This will toggle the modal with a map in it (but not displaying). Resize your window. Voila!
I tried many ways to use javascript and jQuery to trigger a resize event along the lines of:
$('#featinfo').on('shown.bs.modal', function () {
ol.Map.event.trigger(map5, "resize"); //borrowed from google.maps.event. How to do this in ol3?
});
Help?
I had the same problem. You need to force the map to load after the modal has been open.
Try having a function that inits the map:
function loadMap () {
var map = new ol.Map({
controls: ol.control.defaults({
attributionOptions: /** #type {olx.control.AttributionOptions} */ ({
collapsible: false
})
}).extend([mousePositionControl]),
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
],
target: 'map',
view: new ol.View({
center: [0, 0],
zoom: 2
})
});
}
And shown.bs.modal is the event trigered once the modal is opened
<script>
$('#GazModal').on('shown.bs.modal', function () {
loadMap();
})
</script>
Have you tried:
map.updateSize();
Make sure you set a height and width for the Open Layers map container..
#map {
width:100%;
height:500px;
}
Demo: http://www.bootply.com/68637
In the controller of my modal I create the map when the rendered promise from $modalInstance is resolved
$modalInstance.rendered.then(function () {
createMap();
});
It seems that the creation of the modal and the map is happening almost simultaneously so when you create the map it can't find the div element to create it. I put a small timeout after the creation of the modal to solve it
setTimeout(() => {
const map2 = new Map({
target: 'scanmap',
layers: [
new TileLayer({ source: new OSM() })
],
view: new View({
center: [0, 0],
zoom: 6
})
});
}, 100);
In an AngularJS application i had the same problem.
I resolved it by adding a little timout on $onInit()
constructor(private $log: ILogService, private $window: any, private $timeout: ITimeoutService) {
}
$onInit() {
// we need a timout here to load map after the modal!
this.$timeout(() => {
this.initMyMap();
});
}