Skip to content

Commit 6ecb8d5

Browse files
committed
Allow changing building spatial coordinates
Allows updating incorrect or unsuccessful geocoding results
1 parent 12f649b commit 6ecb8d5

File tree

12 files changed

+230
-30
lines changed

12 files changed

+230
-30
lines changed

app/javascript/controllers/better_together/map_controller.js

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,50 @@ export default class extends Controller {
4343

4444
this.addPointsWithLabels(this.spacesValue)
4545

46-
// this.map.on('click', (e) => {
47-
// console.log(`Map clicked at: ${e.latlng}`)
48-
// })
46+
this.map.on('click', (e) => {
47+
if (e.originalEvent.target.closest('.map-controls')) {
48+
return // Ignore clicks on elements inside .map-controls
49+
}
50+
console.log(`Map clicked at: ${e.latlng}`)
51+
const event = new CustomEvent('map:clicked', {
52+
detail: { latlng: e.latlng }
53+
})
54+
this.element.dispatchEvent(event)
55+
})
56+
57+
// Listen for marker:add event
58+
this.element.addEventListener('marker:add', (event) => {
59+
const { id, latlng } = event.detail
60+
const marker = L.marker(latlng).addTo(this.map)
61+
marker.id = id
62+
63+
// Enable dragging and emit marker:moved event on drag end
64+
marker.on('dragend', (e) => {
65+
const { lat, lng } = e.target.getLatLng()
66+
this.element.dispatchEvent(new CustomEvent('marker:moved', {
67+
detail: { id, lat, lng }
68+
}))
69+
})
70+
71+
marker.dragging.enable()
72+
this.map._markers = this.map._markers || {}
73+
this.map._markers[id] = marker
74+
})
75+
76+
// Listen for marker:remove event
77+
this.element.addEventListener('marker:remove', (event) => {
78+
const { id } = event.detail
79+
if (this.map._markers && this.map._markers[id]) {
80+
this.map.removeLayer(this.map._markers[id])
81+
delete this.map._markers[id]
82+
}
83+
})
84+
85+
// Emit map:ready event
86+
const readyEvent = new CustomEvent('map:ready', {
87+
detail: { map: this.map }
88+
})
89+
this.element.dispatchEvent(readyEvent)
4990
}
5091

5192
switchToOSM() {
@@ -106,7 +147,12 @@ export default class extends Controller {
106147
return marker
107148
})
108149

109-
const bounds = L.latLngBounds(points.map(point => [point.lat, point.lng]))
110-
this.map.fitBounds(bounds, { padding: [50, 50] }) // Add padding to ensure points are visible
150+
if (points.length === 1) {
151+
const singlePoint = points[0]
152+
this.map.setView([singlePoint.lat, singlePoint.lng], this.zoomValue) // Adjust zoom level for a single point
153+
} else {
154+
const bounds = L.latLngBounds(points.map(point => [point.lat, point.lng]))
155+
this.map.fitBounds(bounds, { padding: [50, 50] }) // Add padding to ensure points are visible
156+
}
111157
}
112158
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { Controller } from '@hotwired/stimulus'
2+
3+
export default class extends Controller {
4+
static targets = ['latitude', 'longitude', 'map']
5+
6+
connect() {
7+
console.log('SpaceFieldController connected')
8+
9+
// Listen for map click events to set latitude and longitude
10+
this.mapTarget.addEventListener('map:clicked', (event) => {
11+
this.handleMapClick(event.detail)
12+
})
13+
14+
// Initialize marker if latitude and longitude are already set
15+
const lat = parseFloat(this.latitudeTarget.value)
16+
const lng = parseFloat(this.longitudeTarget.value)
17+
if (!isNaN(lat) && !isNaN(lng)) {
18+
this.mapTarget.addEventListener('map:ready', () => {
19+
this.updateMarker([lat, lng])
20+
}, { once: true })
21+
}
22+
}
23+
24+
handleMapClick(event) {
25+
const { lat, lng } = event.latlng
26+
27+
const markerClicked = event.originalEvent ? event.originalEvent.target.classList.contains('leaflet-marker-icon') : false
28+
29+
if (!markerClicked) {
30+
if (confirm(`Set latitude to ${lat.toFixed(6)} and longitude to ${lng.toFixed(6)}?`)) {
31+
this.latitudeTarget.value = lat.toFixed(6)
32+
this.longitudeTarget.value = lng.toFixed(6)
33+
this.updateMarker([lat, lng])
34+
}
35+
}
36+
}
37+
38+
updateMarker(latlng) {
39+
const markerId = `marker-${latlng[0].toFixed(6)}-${latlng[1].toFixed(6)}`
40+
41+
if (this.marker) {
42+
this.mapTarget.dispatchEvent(new CustomEvent('marker:remove', {
43+
detail: { id: this.marker.id }
44+
}))
45+
}
46+
47+
this.marker = { id: markerId, latlng }
48+
this.mapTarget.dispatchEvent(new CustomEvent('marker:add', {
49+
detail: { id: markerId, latlng }
50+
}))
51+
52+
this.mapTarget.addEventListener('marker:moved', (event) => {
53+
if (event.detail.id === markerId) {
54+
const { lat, lng } = event.detail
55+
this.latitudeTarget.value = lat.toFixed(6)
56+
this.longitudeTarget.value = lng.toFixed(6)
57+
}
58+
})
59+
}
60+
61+
latitudeTargetChanged() {
62+
this.syncMarkerWithFields()
63+
}
64+
65+
longitudeTargetChanged() {
66+
this.syncMarkerWithFields()
67+
}
68+
69+
syncMarkerWithFields() {
70+
const lat = parseFloat(this.latitudeTarget.value)
71+
const lng = parseFloat(this.longitudeTarget.value)
72+
73+
if (!isNaN(lat) && !isNaN(lng)) {
74+
this.updateMarker([lat, lng])
75+
}
76+
}
77+
}

app/models/better_together/geography/geospatial_space.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,17 @@ class GeospatialSpace < ApplicationRecord
99

1010
belongs_to :geospatial, polymorphic: true
1111
belongs_to :space, class_name: 'BetterTogether::Geography::Space'
12+
accepts_nested_attributes_for :space
13+
14+
def self.permitted_attributes(id: false, destroy: false, exclude_extra: false)
15+
super + [{
16+
space_attributes: BetterTogether::Geography::Space.permitted_attributes(id: true, destroy: true)
17+
}]
18+
end
19+
20+
def space
21+
super || build_space(creator_id: geospatial&.creator_id, identifier: SecureRandom.alphanumeric(10))
22+
end
1223
end
1324
end
1425
end

app/models/better_together/geography/map.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ def center_for_leaflet
7070
"#{center.latitude},#{center.longitude}"
7171
end
7272

73+
def leaflet_points
74+
mappable&.leaflet_points || []
75+
end
76+
7377
def spaces_for_leaflet
7478
spaces.map(&:to_leaflet_point).compact.to_json
7579
end

app/models/better_together/geography/space.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ class Space < ApplicationRecord
1616
validates :latitude, numericality: { greater_than_or_equal_to: -90, less_than_or_equal_to: 90 }
1717
validates :longitude, numericality: { greater_than_or_equal_to: -180, less_than_or_equal_to: 180 }
1818

19+
def self.permitted_attributes(id: false, destroy: false, exclude_extra: false)
20+
super + %i[longitude latitude elevation]
21+
end
22+
1923
def geocoded?
2024
latitude.present? && longitude.present?
2125
end

app/models/concerns/better_together/geography/geospatial/one.rb

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,26 @@ module One # rubocop:todo Style/Documentation
1111
dependent: :destroy
1212
has_one :space, through: :geospatial_space
1313

14-
accepts_nested_attributes_for :space, reject_if: :all_blank?, allow_destroy: true
15-
16-
# after_create :ensure_space_presence
17-
# after_update :ensure_space_presence
14+
accepts_nested_attributes_for :geospatial_space, :space, reject_if: :all_blank, allow_destroy: true
1815

1916
delegate :latitude, :longitude, :elevation, :geocoded, to: :space, allow_nil: true
2017
delegate :latitude=, :longitude=, :elevation=, to: :space
2118
delegate :latitude_changed?, :longitude_changed?, :elevation_changed?, to: :space, allow_nil: true
2219
end
2320

24-
def ensure_space_presence
25-
return if space.persisted?
21+
class_methods do
22+
def extra_permitted_attributes
23+
super + [{
24+
geospatial_space_attributes:
25+
BetterTogether::Geography::GeospatialSpace.permitted_attributes(id: true,
26+
destroy: true),
27+
space_attributes: BetterTogether::Geography::Space.permitted_attributes(id: true, destroy: true)
28+
}]
29+
end
30+
end
2631

27-
create_space(creator_id: creator_id)
32+
def geospatial_space
33+
super || build_geospatial_space(geospatial: self)
2834
end
2935

3036
def space

app/policies/better_together/geography/map_policy.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ def create?
1616
end
1717

1818
def update?
19-
user.present? && !record.protected? && (record.creator == agent || permitted_to(:manage_platform))
19+
user.present? && !record.protected? && (record.creator == agent || permitted_to?(:manage_platform))
2020
end
2121

2222
def destroy?
23-
user.present? && !record.protected? && (record.creator == agent || permitted_to(:manage_platform))
23+
user.present? && !record.protected? && (record.creator == agent || permitted_to?(:manage_platform))
2424
end
2525

2626
class Scope < Scope # rubocop:todo Style/Documentation

app/views/better_together/geography/maps/_form.html.erb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@
3434
<%= privacy_field(form:, klass: resource_class) %>
3535
</div>
3636

37+
<div class="form-group mb-3"></div>
38+
<%= required_label form, :zoom, class: "form-label" %>
39+
<%= form.number_field :zoom, class: "form-control", required: true, min: 0, max: 20 %>
40+
</div>
41+
3742
<div class="form-group">
3843
<%= form.submit class: "btn btn-primary" %>
3944
</div>

app/views/better_together/geography/maps/_map.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<div class="col-md-4 mb-4">
1+
<div id="<%= dom_id(map) %>" class="col-md-4 mb-4">
22
<div class="card">
33
<div class="card-body">
44
<h5 class="card-title"><%= map.title %></h5>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<%# locals: (form:, map: BetterTogether::Geography::Map.new) %>
2+
<div class="row" data-controller="better_together--space-fields">
3+
<div class="col col-md-6">
4+
<%= form.label :latitude, "Latitude" %>
5+
<%= form.number_field :latitude, step: 0.000001, class: "form-control", placeholder: "Latitude", 'data-better_together--space-fields-target' => "latitude" %>
6+
</div>
7+
<div class="col col-md-6">
8+
<%= form.label :longitude, "Longitude" %>
9+
<%= form.number_field :longitude, step: 0.000001, class: "form-control", placeholder: "Longitude", 'data-better_together--space-fields-target' => "longitude" %>
10+
</div>
11+
<div class="col col-md-12 mt-3">
12+
<div class="map" id="<%= dom_id(map) %>"
13+
data-controller="better_together--map"
14+
data-better_together--map-center-value="<%= map.center_for_leaflet %>"
15+
data-better_together--map-spaces-value="<%= map.leaflet_points.to_json %>"
16+
data-better_together--map-zoom-value="<%= map.zoom %>"
17+
data-better_together--map-extent-value="<%= map.viewport %>"
18+
data-better_together--space-fields-target="map">
19+
<div class="map-controls">
20+
<button type="button" class="btn btn-secondary" data-action="better_together--map#switchToOSM">Map</button>
21+
<button type="button" class="btn btn-secondary" data-action="better_together--map#switchToSatellite">Satellite</button>
22+
<button type="button" class="btn btn-secondary" data-action="better_together--map#enableGeolocation">Geolocate Me</button>
23+
</div>
24+
</div>
25+
</div>
26+
</div>
27+
28+

0 commit comments

Comments
 (0)