Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reimplements Geocoder/Location Search #285

Merged
merged 16 commits into from
Jun 2, 2024
Merged
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..."
maximize: Zoom auf alle Objekte
upload: GeoJSON hochladen
fullscreen: Vollbildmodus umschalten
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 @@ -96,6 +98,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..."
maximize: "Zoom to all features"
upload: "Upload GeoJSON"
fullscreen: "Toggle full-screen"
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 @@ -96,6 +98,9 @@ ja:
control:
geocoding: 住所検索
geolocation: 現在地へ移動
search_location: "Search location"
reverse_location: "Click on the map to get location..."
search_placeholder: "Type a location..."
maximize: 地物にズーム
upload: GeoJSONのアップロード
fullscreen: フルスクリーン切り替え
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
Loading