Skip to content

Commit

Permalink
Merge pull request #285 from gtt-project/dkastl/issue98
Browse files Browse the repository at this point in the history
Reimplements Geocoder/Location Search
  • Loading branch information
dkastl authored Jun 2, 2024
2 parents 9fc8707 + bb4a05c commit 62fd609
Show file tree
Hide file tree
Showing 15 changed files with 483 additions and 151 deletions.
1 change: 0 additions & 1 deletion app/helpers/gtt_map_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ def map_tag(map: nil, layers: map&.layers,
data[:popup] = popup if popup
data[:upload] = upload
data[:collapsed] = collapsed if collapsed
data[:geocoding] = true if Setting.plugin_redmine_gtt['enable_geocoding_on_map'] == 'true'

uid = "ol-" + rand(36**8).to_s(36)

Expand Down
40 changes: 40 additions & 0 deletions app/views/settings/gtt/_geocoder.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@
<%= check_box_tag 'settings[enable_geocoding_on_map]', true, @settings[:enable_geocoding_on_map] %>
</p>

<p>
<%= content_tag(:label, l(:geocoder_provider)) %>
<%= select_tag 'settings[default_geocoder_provider]',
options_for_select([
['Google', 'google'],
['Nominatim (OSM)', 'nominatim'],
['Photon', 'photon'],
['Custom', 'custom', {disabled: true}]
], @settings['default_geocoder_provider']),
include_blank: true %>
<%= link_to t('geocoder_load_example'), '#', id: 'geocoder_load_example', class: 'info' %>
</p>

<p>
<%= content_tag(:label, l(:geocoder_options)) %>
<%= text_area_tag('settings[default_geocoder_options]',
Expand All @@ -15,3 +28,30 @@
:cols => 100) %>
</p>
</div>

<script>
document.getElementById('geocoder_load_example').addEventListener('click', (event) => {
event.preventDefault();
const provider = document.getElementById('settings_default_geocoder_provider').value;
const example = geocoder_examples.find((example) => example.name === provider);
document.getElementById('settings_default_geocoder_options').value = example?.options ? JSON.stringify(example.options, undefined, 2) : "{}";
});

const geocoder_examples = [{
'name': 'nominatim',
'options': {}
}, {
'name': 'google',
'options': {
'apiKey': 'YOUR_API_KEY'
}
}, {
'name': 'photon',
'options': {}
}, {
'name': 'custom',
'options': {
'url': 'https://example.com/geocoder'
}
}];
</script>
7 changes: 6 additions & 1 deletion config/locales/de.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ de:
label_gtt_select_icon: Icon auswählen
label_parameters: Parameter
label_tab_geocoder: Geocoder
geocoder_options: Geocoder Optionen
geocoder_provider: "Anbieter"
geocoder_options: Optionen
geocoder_load_example: "Load example options"
gtt_settings_general_maxzoom_level: Standardwert für die maximale Zoomstufe der
Karte
label_default_collapsed_issues_page_map: Standardmäßig ausgeblendete Karte für Tickets
Expand Down Expand Up @@ -56,6 +58,9 @@ de:
control:
geocoding: Standort-Suche
geolocation: Mein Standort
search_location: "Search location"
reverse_location: "Click on the map to get location..."
search_placeholder: "Type a location..."
geolocation_notification_activated: "Geolocation activated"
geolocation_notification_deactivated: "Geolocation deactivated"
maximize: Zoom auf alle Objekte
Expand Down
7 changes: 6 additions & 1 deletion config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ en:
label_tab_general: "General"
label_tab_geocoder: "Geocoder"

geocoder_options: "Geocoder Options"
geocoder_provider: "Provider"
geocoder_options: "Options"
geocoder_load_example: "Load example options"

gtt_map_rotate_label: "Map rotation"
gtt_map_rotate_info_html: "Hold down <code>Shift+Alt</code> and drag the map to rotate."
Expand Down Expand Up @@ -100,6 +102,9 @@ en:
control:
geocoding: "Location search"
geolocation: "My location"
search_location: "Search location"
reverse_location: "Click on the map to get location..."
search_placeholder: "Type a location..."
geolocation_activated: "Geolocation activated"
geolocation_deactivated: "Geolocation deactivated"
maximize: "Zoom to all features"
Expand Down
7 changes: 6 additions & 1 deletion config/locales/ja.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ ja:
label_tab_general: "一般"
label_tab_geocoder: "ジオコーダ"

geocoder_options: "ジオコーダのオプション"
geocoder_provider: "Provider"
geocoder_options: "オプション"
geocoder_load_example: "Load example options"

gtt_map_rotate_label: "地図の回転"
gtt_map_rotate_info_html: "<code>Shift+Alt</code> を押しながらドラッグして地図を回転します。"
Expand Down Expand Up @@ -100,6 +102,9 @@ ja:
control:
geocoding: 住所検索
geolocation: 現在地へ移動
search_location: "Search location"
reverse_location: "Click on the map to get location..."
search_placeholder: "Type a location..."
geolocation_notification_activated: "Geolocation activated"
geolocation_notification_deactivated: "Geolocation deactivated"
maximize: 地物にズーム
Expand Down
20 changes: 12 additions & 8 deletions lib/redmine_gtt/hooks/view_layouts_base_html_head_hook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,19 @@ def view_layouts_base_html_head(context={})

def view_layouts_base_body_bottom(context={})
tags = [];
geocoder = {}
geocoder_options = Setting.plugin_redmine_gtt['default_geocoder_options']
if geocoder_options.present?
begin
geocoder = JSON.parse(geocoder_options)
rescue JSON::ParserError => exception
Rails.logger.warn "Failed to parse setting's 'geocoder_options' as JSON: #{exception}\nUse default '{}' instead."
end

geocoder = {
enabled: false
}

if Setting.plugin_redmine_gtt['enable_geocoding_on_map'] == 'true'
geocoder = {
enabled: true,
provider: Setting.plugin_redmine_gtt['default_geocoder_provider'],
options: (JSON.parse(Setting.plugin_redmine_gtt['default_geocoder_options']) rescue {})
}
end

tags.push(tag.div :data => {
:lon => Setting.plugin_redmine_gtt['default_map_center_longitude'],
:lat => Setting.plugin_redmine_gtt['default_map_center_latitude'],
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"devDependencies": {
"@types/fontfaceobserver": "^2.1.3",
"@types/geojson": "^7946.0.14",
"@types/google.maps": "^3.55.9",
"@types/jquery": "^3.5.29",
"@types/jqueryui": "^1.12.21",
"@types/ol-ext": "npm:@siedlerchr/types-ol-ext",
Expand Down
62 changes: 62 additions & 0 deletions src/components/gtt-client/geocoding/CustomButtonMixin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import ol_ext_element from 'ol-ext/util/element';

export function applyCustomButton(searchControl: any, options: any) {
// Remove the default button if it exists
const defaultButton = searchControl.element.querySelector('button[type="button"]');
if (defaultButton) {
defaultButton.remove();
}

// Create a custom search button with a custom icon
searchControl.button = ol_ext_element.create('BUTTON', {
className: 'ol-search-gtt',
title: options.title || 'Search',
html: options.html || '<i class="mdi mdi-map-search-outline"></i>',
parent: searchControl.element,
click: function () {
searchControl.element.classList.toggle('ol-collapsed');
if (!searchControl.element.classList.contains('ol-collapsed')) {
const input = searchControl.element.querySelector('input.search');
if (input) {
input.focus();
searchControl.drawList_();
}
}
}.bind(searchControl)
}) as HTMLButtonElement;

// Handle the reverse button if reverse geocoding is enabled
if (options.providerOptions.reverse) {
// Remove the default reverse button if it exists
const defaultReverseButton = searchControl.element.querySelector('button.ol-revers');
if (defaultReverseButton) {
defaultReverseButton.remove();
}

// Create a custom reverse button with a custom icon
searchControl.reverseButton = ol_ext_element.create('BUTTON', {
className: 'ol-search-gtt-reverse ol-revers',
title: options.providerOptions.reverseTitle || 'Click on the map',
html: options.html_reverse || 'X',
parent: searchControl.element,
click: function () {
if (!searchControl.get('reverse')) {
searchControl.set('reverse', !searchControl.get('reverse'));
const input = searchControl.element.querySelector('input.search');
if (input) {
input.focus();
searchControl.element.classList.add('ol-revers');
}
} else {
searchControl.set('reverse', false);
}
}.bind(searchControl)
}) as HTMLButtonElement;
}

// Move list to the end
const ul = searchControl.element.querySelector("ul.autocomplete");
if (ul) {
searchControl.element.appendChild(ul);
}
}
58 changes: 58 additions & 0 deletions src/components/gtt-client/geocoding/SearchFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// src/components/gtt-client/geocoding/SearchFactory.ts
import { applyCustomButton } from './CustomButtonMixin';
import SearchGTT from './SearchGTT';
import SearchGoogle from './SearchGoogle';
import SearchNominatim from 'ol-ext/control/SearchNominatim';
import SearchPhoton from 'ol-ext/control/SearchPhoton';

export function createSearchControl(options: any): any {
let searchControl: any;

// Create search control instance based on the provider
switch (options.provider) {
// Apply settings for Nomatim provider
case 'nominatim':
options.providerOptions = {
reverse: true, // Enable reverse geocoding
typing: -1, // Disable typing delay (see Nominatim policy!)
...options.providerOptions,
};
searchControl = new SearchNominatim(options.providerOptions);
break;
// Apply settings for Photon provider
case 'photon':
options.providerOptions = {
// lang: 'en', // Force preferred language
reverse: true, // Enable reverse geocoding
position: true, // Priority to position
...options.providerOptions,
};
searchControl = new SearchPhoton(options.providerOptions);
break;
// Apply settings for Google provider
case 'google':
options.providerOptions = {
reverse: true, // Enable reverse geocoding
...options.providerOptions,
};
searchControl = new SearchGoogle(options.providerOptions);
break;

case 'custom':
options.providerOptions = {
...options.providerOptions,
};
searchControl = new SearchGTT(options.providerOptions);
break;
// Add cases for new providers here
default:
// Throw an error if the provider is not supported
throw new Error(`Unsupported provider: ${options.provider}`);
break;
}

// Apply custom button implementation
applyCustomButton(searchControl, options);

return searchControl;
}
22 changes: 22 additions & 0 deletions src/components/gtt-client/geocoding/SearchGTT.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// src/components/gtt-client/geocoding/SearchGTT.ts
import Search, { Options as SearchOptions } from 'ol-ext/control/Search';

interface SearchGTTOptions extends SearchOptions {
// Add custom options here
}

/**
* Use this as a starting point for supporting a new geocoding service.
*/
class SearchGTT extends Search {
public button: HTMLButtonElement;

constructor(options: SearchGTTOptions = {}) {
options = options || {};
options.className = options.className || 'ol-search-gtt';

super(options);
}
}

export default SearchGTT;
Loading

0 comments on commit 62fd609

Please sign in to comment.