How to integrate Geodjango with Google Maps API 3? - javascript

I have a geodjango queryset containing several fields but want to use only user_name and location (a point field) which I want to use as a marker in google maps API 3.
Bear with me as I don’t know Javascript and I have a series of questions.
Take this as conceptual brainstorming for a novice:
My SO search suggests that I need to serialize the queryset objects
to JSON. I use the built-in serializer module to convert to JSON.
I think the JSON objects are converted in views.py (let’s
call it json_data). Are these JSON objects stored in the PostGIS database? Wouldn’t that be redundant?
Furthermore, how do I reference them in the map.js (google maps
API 3) javascript file? I want to (import?link?) JSON objects to display them as location markers.
I want to know how to declare and iterate the javascript variable
locations.
For var(i=0;i< locations.length;i++){[
[json_data.user_name, json_data.point],
]
var map = new google.maps.Map(document.getElementById('map'), {
center: new google.maps.LatLng(49.279504, -123.1162),
zoom: 14,
mapTypeId: google.maps.MapTypeId.ROADMAP
});
var infowindow = new google.maps.InfoWindow();
var marker, i;
for (i = 0; i < locations.length; i++) {
marker = new google.maps.Marker({
position: new google.maps.LatLng(locations[i][1], locations[i][2]),
map: map,
icon: 'http://maps.google.com/mapfiles/ms/icons/blue-dot.png'
});
google.maps.event.addListener(marker, 'click', (function(marker, i) {
return function() {
infowindow.setContent(locations[i][0]);
infowindow.open(map, marker);
}
})(marker, i));
}
Guide me if I went unnecessarily convoluted way to do a simple task.

TL;DR
No, what you are doing is not redundant and nothing get's written to the database from those answers.
You need to make a getJSON() or similar call to your API's endpoint to access the data.
You can do it on the 2nd step's call and declare it as a list.
What you are thinking is pretty much correct but there is room for improvement (thus the long answer below).
Answer:
Some time ago I read a very good initiation tutorial on building a GIS application with geodjango and google maps. Read it and it should give you a great jump start.
After you read that we will follow a somewhat different way which leaves more room to play around with your front-end (use react for example or whatever comes to mind).
The back-end:
Create a view to retrieve the information you want (user_name, location) as JSON, using the values() queryset method which returns a list of dictionaries.
Since we have to JSONify a list, we will use JsonResponse and we will mark it as unsafe:
from django.http import JsonResponse
def my_view(request):
resp = MyModel.objects.all().values('user_name', 'location')
return JsonResponse(list(resp), safe=False)
Add an endpoint to access that view on urls.py:
urlpatterns = [
...
url(r'^my_endpoint/$', my_view, name='my_endpoint'),
...
]
Now whenever we access the my_endpoint/ we will get a JSON representation of every object's user_name and location in our database which will look like this:
[
{user_name: a_user, location: [lat, lng]},
{user_name: another_user, location: [lat, lng]},
...
]
Moving to the front-end now:
Make a getJSON() or an ajax() or any other type of call to the API and in the same time create a marker list (close to what #MoshFeu suggests):
let map = new google.maps.Map(document.getElementById('map'), {
center: new google.maps.LatLng(49.279504, -123.1162),
zoom: 14,
mapTypeId: google.maps.MapTypeId.ROADMAP
});
let markers = [];
$.getJSON( "my_base_url/my_endpoint", function(data) {
$.each(data, function() {
markers.push(
new google.maps.Marker({
position: {
lat: data['location'][0],
lng: data['location'][1]
},
map: map,
icon: 'http://maps.google.com/mapfiles/ms/icons/blue-dot.png'
})
);
});
});
...
And we are pretty much done!
You don't need to make any special serialization to your data.
You can query the data from any type of front-end you can imagine which gives you designing freedom.

My use-case. I used django.contrib.gis (django.contrib.gis.db.models.PolygonField) and needed to replace default map with Google maps + change default map coordinates, zoom, etc.
TL;DR
I created a new app called gis_addons with custom template and widget to use.
I instructed my model admin (using formfield_overrides) to use my own map widget.
Make sure to add the gis_addons to INSTALLED_APPS.
File: gis_addons/templates/gis_addons/openlayers_googlemaps.html
{% extends "gis/openlayers.html" %}
{% load i18n l10n %}
{% block base_layer %}
var base_layer = new ol.layer.Tile({
source: new ol.source.XYZ({
attributions: [new ol.Attribution({ html: '' })],
maxZoom: 25,
url: "http://mt0.google.com/vt/lyrs=r&hl=en&x={x}&y={y}&z={z}&s=Ga"
})
});
{% endblock %}
{% block options %}var options = {
base_layer: base_layer,
geom_name: '{{ geom_type }}',
id: '{{ id }}',
map_id: '{{ id }}_map',
map_options: map_options,
map_srid: {{ map_srid|unlocalize }},
name: '{{ name }}',
default_lat: 53.2193835,
default_lon: 6.5665018,
default_zoom: 15
};
{% endblock %}
File: gis_addons/widgets.py
from django.contrib.gis.forms.widgets import OpenLayersWidget
class GoogleMapsOpenLayersWidget(OpenLayersWidget):
"""Google Maps OpenLayer widget."""
template_name = 'gis_addons/openlayers_googlemaps.html'
File: my_app/models.py
from django.db import models
from django.contrib.gis.db import models as gis_models
from django.utils.translation import ugettext_lazy as _
class MyModel(models.Model):
# ...
coordinates = gis_models.PolygonField(
verbose_name=_("Geo coordinates"),
)
def __str__(self):
return self.name
File: my_app/admin.py
from django.contrib import admin
from django.contrib.gis.db.models import PolygonField
from gis_addons.widgets import GoogleMapsOpenLayersWidget
from my_app.models import MyModel
#admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin):
# ...
formfield_overrides = {
PolygonField: {"widget": GoogleMapsOpenLayersWidget}
}

Related

How to access ArcGIS JavaScript API outside of require callback?

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

view.goTo map renders blank arcgis

I am working on an arcgis map, I'm trying to update the map center by calling goTo() on my mapview but for some reason the map just changes to be blank and never updates, I am logging the new coordinates and they are correct.
I am using the reference docs here: https://developers.arcgis.com/javascript/latest/api-reference/esri-views-MapView.html
Can someone with some arcgis experience help me out. I know this isn't an issue with my code specifically but it might be an issue with vue and component rendering as it relates to arcgis
so far I have tried
- getting rid of props and updating everything within the component locally
- using keys to force re-render the component
as an interesting note, if I just enter in some magic numbers for my new location the map updates correctly, however when i use some function to get the location and then pass it in, it does not work and just shows as a blank map
my app.vue
<template>
<div id="app">
<web-map v-bind:centerX="lat" v-bind:centerY="long" ref="map"/>
<div class="center">
<b-button class="btn-block" #click="updateCenter()" variant="primary">My Location</b-button>
</div>
</div>
</template>
<script>
import WebMap from './components/webmap.vue';
export default {
name: 'App',
components: { WebMap },
data(){
return{
lat: null,
long: null,
}
},
methods:{
updateCenter(){
this.$refs.map.getLocation()
}
},
};
</script>
my map component
<template>
<div></div>
</template>
<script>
import { loadModules } from 'esri-loader';
export default {
name: 'web-map',
data: function(){
return{
X: -118,
Y: 34,
}
},
mounted() {
console.log('new data',this.X,this.Y)
// lazy load the required ArcGIS API for JavaScript modules and CSS
loadModules(['esri/Map', 'esri/views/MapView'], { css: true })
.then(([ArcGISMap, MapView]) => {
const map = new ArcGISMap({
basemap: 'topo-vector'
});
this.view = new MapView({
container: this.$el,
map: map,
center: [-118,34], ///USE PROPS HERE FOR NEW CENTER
zoom: 8
});
});
},
beforeDestroy() {
if (this.view) {
// destroy the map view
this.view.container = null;
}
},
methods:{
showPos(pos){
console.log('new location',pos.coords.latitude,pos.coords.longitude)
this.view.goTo({center:[pos.coords.latitude,pos.coords.longitude]})
},
getLocation(){
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(this.showPos);
} else {
console.log("Geolocation is not supported by this browser.");
}
},
}
};
</script>
Switch:
this.view = new MapView({
container: this.$el,
map: map,
center: [-118,34], ///USE PROPS HERE FOR NEW CENTER
zoom: 8
});
to
this.view = new MapView({
container: this.$el,
map: map });
this.view.center.longitude = -118;
this.view.center.latitude = 34;
this.view.zoom = 8;
The other answer by Tao has the long/latitude backwards in the .goTo({center: []}) method call, which is why it goes to the ocean: https://developers.arcgis.com/javascript/latest/api-reference/esri-views-MapView.html#goTo
Here's something that works:
https://codesandbox.io/s/frosty-glitter-39wpe?file=/src/App.vue
I made it from scratch, only taking small bits from yours and combining them with some examples from ArcGIS (which I'm not familiar with, at all).
One thing to note is that the .goTo({center: [lat, long]}) didn't work as expected: it kept centering in the middle of some ocean.
I then imported Point from esri and passed the center as new Point(long, lat), which seems to produce the expected result. Since it works, I haven't looked further, but I guess it should be doable without the conversion. You probably need to pass in the coordinates system or something along these lines.
As far as I can tell, what's wrong in your example is the way you try to pass data down from parent to child. You expect this.$refs.map to be a Vue instance, but it's not. It's a DOM element. It's basically the Vue instance's $el. Accessing child methods from parent instance is not so straight forward.
Another thing to notice is that, even though you bind centerX and centerY on child in your example, you never seem to use them (but I guess that's just a left over from when you tried with props !?).
Anyways, in my example, I chose to simply update the coords prop of the children while having a watch fn to handle re-centering.

insert a url/path into google maps marker, Laravel

I am trying to insert a url/path into google map marker. So I tried google map's url: but no effects. when mouse on the marker it changes to pointer but I can't click or the page doesn't open.
By the way I try to make path to detail screen of each id.
var marker = new google.maps.Marker({
map: map,
icon: 'imgs/marker.png',
label: {
text: estates.data[0].price,
color: "#fff",
},
url: "/pages/{{$est->id}}",
position: {
lat: {{$est->lat}},
lng: {{$est->lng}}
}
});
The way I am using it wrong?
Controller:
public function details($id)
{
$estates = allestates::where('id', $id)->first();
// $rsltActualPrice = (int)$estates->extend / (int)$estates->price;
return view('pages.details', compact('estates'));
}
and Route:
Route::get('/pages/{id}', 'PagesController#details');
You may want to create a route like :
Route::get('/pages/{estateId}', 'yourController#index')->name('map');
and change your url address to this :
url: route('map', ['estateId' => $est->id]),

ember-leaflet: how to refresh component data?

I'm using ember-leaflet to display Leaflet maps in my Ember application. In my case, the map is used to display an appointment location. The user selects an appointment from a list and the corresponding appointment details (including the map) are then displayed.
When an appointment is selected, the details are passed to a component, which contains the map. Unfortunately, after the user selects an appointment and then another is selected, the coordinates on the map do not change. However, outputting the coordinates using Handlebars, I can in fact see that the different coordinates are being passed. That leads me to believe that the map needs to be "refreshed" somehow in order for the new coordinates to be displayed on the map.
I use the component like so: {{ember-leaflet geoJSON=geoJSON}} where geoJSON is a string containing the location data.
My component is as follows:
// components/leaflet-map.js
import Ember from 'ember';
import ENV from '../config/environment';
import EmberLeafletComponent from 'ember-leaflet/components/leaflet-map';
import MarkerCollectionLayer from 'ember-leaflet/layers/marker-collection';
import TileLayer from 'ember-leaflet/layers/tile';
L.Icon.Default.imagePath = '/images/leaflet';
export default EmberLeafletComponent.extend({
center: Ember.computed(function() {
return this.get('coordinates');
}),
/////////////////////////////////////
// PROPERTIES
/////////////////////////////////////
geoJSON: null,
/////////////////////////////////////
// COMPUTED PROPERTIES
/////////////////////////////////////
childLayers: Ember.computed('coordinates', function() {
return [
TileLayer.extend({
tileUrl: 'https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token={accessToken}',
options: {
id: 'XXXXXXX',
accessToken: ENV.APP.MAPBOX_KEY
}
}),
MarkerCollectionLayer.extend({
content: [{ location: this.get('coordinates') }]
})
];
}),
coordinates: Ember.computed('geoJSON', function() {
if (this.get('geoJSON')) {
const coordinates = JSON.parse(this.get('geoJSON')).coordinates;
if (coordinates) {
return L.latLng(coordinates[1], coordinates[0]);
}
}
return null;
}),
});
When setting a breakpoint, it appears that childLayers and coordinates are only being called once regardless of which appointment is selected by the user. I've considered setting up an observer to observe the geoJSON property, but it seems overkill.
Any help would be greatly appreciated!

use javascript and geojson to create feature layer for mapbox map

I have a mapbox map in my Ruby on Rails app, and I'd like to populate a feature layer with point coordinates from my database. I can't seem to find meaningful help on this on the Mapbox website. I found a website that offered a guide, but it skips steps that must have been obvious to the author, but aren't to me. Can anyone help me do this?
Here's what I have. In the Index method of my Productions controller, I've created a geojson array (the indentation here is screwed up, but it's correct in the actual file):
def index
# to place all productions on map
#productionsall = Production.all
#geojson = Array.new
#productionsall.each do |p|
#geojson << {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [p.longitude, p.latitude]
},
properties: {
name: p.company,
address: p.full_address,
:'marker-color' => '#00607d',
:'marker-symbol' => 'circle',
:'marker-size' => 'medium'
}
}
end
respond_to do |format|
format.html
format.json { render json: #geojson } # respond with the created JSON object
end
end
Then on the Index page, I've got the map itself and an attempt at a function (borrowed from a Stack Overflow answer) to add a layer:
<div id='map'></div>
<script>
L.mapbox.accessToken = 'xxxxx';
var map = L.mapbox.map('map', 'example.idkeiso')
.setView([41.344,-99.316], 5);
var zipLayer;
$.getJSON('production/index.json', function(data) {
zipLayer = L.mapbox.featureLayer(data);
zipLayer.addTo(map);
});
</script>
The map appears, but without any markers.
Thanks for your help!

Categories

Resources