/* * 3D City Database - The Open Source CityGML Database * http://www.3dcitydb.org/ * * Copyright 2013 - 2019 * Chair of Geoinformatics * Technical University of Munich, Germany * https://www.gis.bgu.tum.de/ * * The 3D City Database is jointly developed with the following * cooperation partners: * * virtualcitySYSTEMS GmbH, Berlin <http://www.virtualcitysystems.de/> * M.O.S.S. Computer Grafik Systeme GmbH, Taufkirchen <http://www.moss.de/> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.citydb.gui.components.mapviewer; import org.citydb.config.Config; import org.citydb.config.geometry.BoundingBox; import org.citydb.config.gui.window.GeocodingServiceName; import org.citydb.config.gui.window.WindowSize; import org.citydb.config.i18n.Language; import org.citydb.config.project.database.Database; import org.citydb.config.project.database.Database.PredefinedSrsName; import org.citydb.config.project.database.DatabaseSrs; import org.citydb.event.Event; import org.citydb.event.EventHandler; import org.citydb.event.global.EventType; import org.citydb.gui.components.bbox.BoundingBoxClipboardHandler; import org.citydb.gui.components.bbox.BoundingBoxListener; import org.citydb.gui.components.mapviewer.geocoder.Geocoder; import org.citydb.gui.components.mapviewer.geocoder.GeocoderResult; import org.citydb.gui.components.mapviewer.geocoder.Location; import org.citydb.gui.components.mapviewer.geocoder.LocationType; import org.citydb.gui.components.mapviewer.geocoder.service.GeocodingService; import org.citydb.gui.components.mapviewer.geocoder.service.GeocodingServiceException; import org.citydb.gui.components.mapviewer.geocoder.service.GoogleGeocoder; import org.citydb.gui.components.mapviewer.geocoder.service.OSMGeocoder; import org.citydb.gui.components.mapviewer.map.DefaultWaypoint; import org.citydb.gui.components.mapviewer.map.DefaultWaypoint.WaypointType; import org.citydb.gui.components.mapviewer.map.Map; import org.citydb.gui.components.mapviewer.map.event.BoundingBoxSelectionEvent; import org.citydb.gui.components.mapviewer.map.event.MapBoundsSelectionEvent; import org.citydb.gui.components.mapviewer.map.event.MapEvents; import org.citydb.gui.components.mapviewer.map.event.ReverseGeocoderEvent; import org.citydb.gui.components.mapviewer.map.event.ReverseGeocoderEvent.ReverseGeocoderStatus; import org.citydb.gui.components.mapviewer.validation.BoundingBoxValidator; import org.citydb.gui.components.mapviewer.validation.BoundingBoxValidator.ValidationResult; import org.citydb.gui.factory.PopupMenuDecorator; import org.citydb.gui.util.GuiUtil; import org.citydb.log.Logger; import org.citydb.plugin.extension.view.ViewController; import org.citydb.registry.ObjectRegistry; import org.jdesktop.swingx.mapviewer.AbstractTileFactory; import org.jdesktop.swingx.mapviewer.GeoPosition; import org.jdesktop.swingx.mapviewer.TileFactory; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFormattedTextField; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JTextPane; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.text.html.HTMLDocument; import java.awt.Color; import java.awt.Desktop; import java.awt.Desktop.Action; import java.awt.Dimension; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Rectangle; import java.awt.Toolkit; import java.awt.event.ItemEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.geom.Point2D; import java.beans.PropertyChangeListener; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.text.MessageFormat; import java.text.ParseException; import java.util.HashSet; import java.util.Locale; import java.util.concurrent.ExecutionException; @SuppressWarnings("serial") public class MapWindow extends JDialog implements EventHandler { private final Logger log = Logger.getInstance(); private static MapWindow instance = null; public static DecimalFormat LAT_LON_FORMATTER = new DecimalFormat("##0.0000000", DecimalFormatSymbols.getInstance(Locale.ENGLISH)); static { LAT_LON_FORMATTER.setMaximumIntegerDigits(3); LAT_LON_FORMATTER.setMinimumIntegerDigits(1); LAT_LON_FORMATTER.setMinimumFractionDigits(2); LAT_LON_FORMATTER.setMaximumFractionDigits(7); } private final Config config; private Map map; private JComboBox<Location> searchBox; private JLabel searchResult; private ImageIcon loadIcon; private volatile boolean updateSearchBox = true; private JFormattedTextField minX; private JFormattedTextField minY; private JFormattedTextField maxX; private JFormattedTextField maxY; private JButton goButton; private JButton applyButton; private JButton cancelButton; private JButton copyBBox; private JButton pasteBBox; private JButton showBBox; private JButton clearBBox; private JLabel bboxTitel; private JLabel reverseTitle; private JLabel reverseInfo; private JTextPane reverseText; private JLabel reverseSearchProgress; private JLabel helpTitle; private JLabel helpText; private JLabel googleMapsTitle; private JButton googleMapsButton; private JLabel geocoderTitle; private JComboBox<GeocodingServiceName> geocoderCombo; private BoundingBoxListener listener; private BBoxPopupMenu[] bboxPopups; private JFrame mainFrame; private BoundingBoxClipboardHandler clipboardHandler; private BoundingBoxValidator validator; private MapWindow(ViewController viewController, Config config) { super(viewController.getTopFrame(), true); this.config = config; // register for events ObjectRegistry.getInstance().getEventDispatcher().addEventHandler(EventType.SWITCH_LOCALE, this); ObjectRegistry.getInstance().getEventDispatcher().addEventHandler(MapEvents.BOUNDING_BOX_SELECTION, this); ObjectRegistry.getInstance().getEventDispatcher().addEventHandler(MapEvents.MAP_BOUNDS, this); ObjectRegistry.getInstance().getEventDispatcher().addEventHandler(MapEvents.REVERSE_GEOCODER, this); mainFrame = viewController.getTopFrame(); clipboardHandler = BoundingBoxClipboardHandler.getInstance(config); validator = new BoundingBoxValidator(this, config); init(); doTranslation(); } public static synchronized MapWindow getInstance(ViewController viewController, Config config) { if (instance == null) instance = new MapWindow(viewController, config); instance.applyButton.setVisible(false); instance.setSizeOnScreen(); // update geocoder GeocodingService service = null; try { service = instance.getGeocodingService(config.getGui().getMapWindow().getGeocoder()); } catch (GeocodingServiceException e) { service = new OSMGeocoder(); config.getGui().getMapWindow().setGeocoder(GeocodingServiceName.OSM_NOMINATIM); } finally { Geocoder.getInstance().setGeocodingService(service); instance.geocoderCombo.setSelectedItem(config.getGui().getMapWindow().getGeocoder()); } return instance; } public static synchronized MapWindow getInstance(ViewController viewController, BoundingBoxListener listener, Config config) { instance = getInstance(viewController, config); if (listener != null) { instance.listener = listener; instance.applyButton.setVisible(true); } return instance; } private void init() { setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/org/citydb/gui/images/map/map_icon.png"))); setLayout(new GridBagLayout()); getContentPane().setBackground(Color.WHITE); Color borderColor = new Color(0, 0, 0, 150); loadIcon = new ImageIcon(getClass().getResource("/org/citydb/gui/images/map/loader.gif")); map = new Map(config); JPanel top = new JPanel(); JPanel left = new JPanel(); // map map.getMapKit().setBorder(BorderFactory.createMatteBorder(1, 2, 0, 0, borderColor)); GridBagConstraints gridBagConstraints = GuiUtil.setConstraints(0, 0, 1, 0, GridBagConstraints.BOTH, 0, 0, 0, 0); gridBagConstraints.gridwidth = 2; add(top, gridBagConstraints); add(left, GuiUtil.setConstraints(0, 1, 0, 0, GridBagConstraints.BOTH, 5, 5, 5, 5)); add(map.getMapKit(), GuiUtil.setConstraints(1, 1, 1, 1, GridBagConstraints.BOTH, 0, 0, 0, 0)); // top components top.setLayout(new GridBagLayout()); top.setBackground(new Color(245, 245, 245)); top.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, borderColor)); goButton = new JButton(); searchBox = new JComboBox<>(); searchResult = new JLabel(); searchResult.setPreferredSize(new Dimension(searchResult.getPreferredSize().width, loadIcon.getIconHeight())); searchBox.setEditable(true); searchBox.setPreferredSize(new Dimension(500, (int)searchBox.getPreferredSize().getHeight())); applyButton = new JButton(); cancelButton = new JButton(); applyButton.setFont(applyButton.getFont().deriveFont(Font.BOLD)); applyButton.setEnabled(false); top.add(searchBox, GuiUtil.setConstraints(0, 0, 0, 0, GridBagConstraints.HORIZONTAL, 10, 10, 0, 5)); top.add(goButton, GuiUtil.setConstraints(1, 0, 0, 0, GridBagConstraints.BOTH, 10, 5, 0, 10)); top.add(Box.createHorizontalGlue(), GuiUtil.setConstraints(2, 0, 1, 0, GridBagConstraints.HORIZONTAL, 10, 5, 0, 0)); top.add(applyButton, GuiUtil.setConstraints(3, 0, 0, 0, GridBagConstraints.BOTH, 10, 0, 0, 5)); top.add(cancelButton, GuiUtil.setConstraints(4, 0, 0, 0, GridBagConstraints.BOTH, 10, 5, 0, 5)); top.add(searchResult, GuiUtil.setConstraints(0, 1, 0, 0, GridBagConstraints.BOTH, 2, 10, 2, 10)); // left components left.setLayout(new GridBagLayout()); left.setBackground(Color.WHITE); Border componentBorder = BorderFactory.createCompoundBorder(UIManager.getBorder("TitledBorder.border"), BorderFactory.createEmptyBorder(5, 5, 5, 5)); // BBox final JPanel bbox = new JPanel(); bbox.setBorder(componentBorder); bbox.setLayout(new GridBagLayout()); bboxTitel = new JLabel(); bboxTitel.setFont(bbox.getFont().deriveFont(Font.BOLD)); bboxTitel.setIcon(new ImageIcon(getClass().getResource("/org/citydb/gui/images/map/selection.png"))); bboxTitel.setIconTextGap(5); final JPanel bboxFields = new JPanel(); bboxFields.setLayout(new GridBagLayout()); minX = new JFormattedTextField(LAT_LON_FORMATTER); minY = new JFormattedTextField(LAT_LON_FORMATTER); maxX = new JFormattedTextField(LAT_LON_FORMATTER); maxY = new JFormattedTextField(LAT_LON_FORMATTER); minX.setBackground(Color.WHITE); minY.setBackground(Color.WHITE); maxX.setBackground(Color.WHITE); maxY.setBackground(Color.WHITE); Dimension dim = new Dimension(90, minX.getPreferredSize().height); minX.setPreferredSize(dim); minY.setPreferredSize(dim); maxX.setPreferredSize(dim); maxY.setPreferredSize(dim); minX.setMinimumSize(dim); minY.setMinimumSize(dim); maxX.setMinimumSize(dim); maxY.setMinimumSize(dim); gridBagConstraints = GuiUtil.setConstraints(0, 0, 0, 0, GridBagConstraints.NONE, 5, 2, 0, 2); gridBagConstraints.gridwidth = 2; gridBagConstraints.anchor = GridBagConstraints.CENTER; bboxFields.add(maxY, gridBagConstraints); gridBagConstraints.gridwidth = 1; gridBagConstraints.gridy = 1; gridBagConstraints.anchor = GridBagConstraints.EAST; bboxFields.add(minX, gridBagConstraints); gridBagConstraints.gridx = 1; gridBagConstraints.anchor = GridBagConstraints.WEST; bboxFields.add(maxX, gridBagConstraints); gridBagConstraints.gridwidth = 2; gridBagConstraints.gridy = 2; gridBagConstraints.gridx = 0; gridBagConstraints.anchor = GridBagConstraints.CENTER; bboxFields.add(minY, gridBagConstraints); // BBox buttons JPanel bboxButtons = new JPanel(); bboxButtons.setLayout(new GridBagLayout()); bboxButtons.setBackground(bbox.getBackground()); showBBox = new JButton(); clearBBox = new JButton(); copyBBox = new JButton(); ImageIcon copyIcon = new ImageIcon(getClass().getResource("/org/citydb/gui/images/common/bbox_copy.png")); copyBBox.setIcon(copyIcon); copyBBox.setMargin(new Insets(1, 1, 1, 1)); copyBBox.setEnabled(false); pasteBBox = new JButton(); ImageIcon pasteIcon = new ImageIcon(getClass().getResource("/org/citydb/gui/images/common/bbox_paste.png")); pasteBBox.setIcon(pasteIcon); pasteBBox.setMargin(new Insets(1, 1, 1, 1)); pasteBBox.setEnabled(clipboardHandler.containsPossibleBoundingBox()); bboxButtons.add(showBBox, GuiUtil.setConstraints(0, 0, 1, 0, GridBagConstraints.HORIZONTAL, 0, 0, 0, 0)); bboxButtons.add(clearBBox, GuiUtil.setConstraints(1, 0, 1, 0, GridBagConstraints.HORIZONTAL, 0, 5, 0, 0)); Box bboxTitelBox = Box.createHorizontalBox(); bboxTitelBox.add(bboxTitel); bboxTitelBox.add(Box.createHorizontalGlue()); bboxTitelBox.add(copyBBox); bboxTitelBox.add(Box.createHorizontalStrut(5)); bboxTitelBox.add(pasteBBox); bbox.add(bboxTitelBox, GuiUtil.setConstraints(0, 0, 1, 0, GridBagConstraints.HORIZONTAL, 0, 0, 2, 0)); bbox.add(bboxFields, GuiUtil.setConstraints(0, 1, 1, 0, GridBagConstraints.HORIZONTAL, 5, 0, 5, 0)); bbox.add(bboxButtons, GuiUtil.setConstraints(0, 2, 1, 0, GridBagConstraints.HORIZONTAL, 10, 0, 0, 0)); // Reverse geocoder JPanel reverse = new JPanel(); reverse.setBorder(componentBorder); reverse.setLayout(new GridBagLayout()); reverseTitle = new JLabel(); reverseTitle.setFont(reverseTitle.getFont().deriveFont(Font.BOLD)); reverseTitle.setIcon(new ImageIcon(getClass().getResource("/org/citydb/gui/images/map/waypoint_small.png"))); reverseTitle.setIconTextGap(5); reverseSearchProgress = new JLabel(); reverseInfo = new JLabel(); reverseText = new JTextPane(); reverseText.setEditable(false); reverseText.setBorder(minX.getBorder()); reverseText.setBackground(Color.WHITE); reverseText.setContentType("text/html"); ((HTMLDocument)reverseText.getDocument()).getStyleSheet().addRule( "body { font-family: " + reverseText.getFont().getFamily() + "; " + "font-size: " + reverseText.getFont().getSize() + "pt; }"); reverseText.setVisible(false); Box reverseTitelBox = Box.createHorizontalBox(); reverseTitelBox.add(reverseTitle); reverseTitelBox.add(Box.createHorizontalGlue()); reverseTitelBox.add(reverseSearchProgress); reverse.add(reverseTitelBox, GuiUtil.setConstraints(0, 0, 1, 0, GridBagConstraints.HORIZONTAL, 0, 0, 2, 0)); reverse.add(reverseText, GuiUtil.setConstraints(0, 1, 1, 0, GridBagConstraints.BOTH, 10, 0, 0, 0)); reverse.add(reverseInfo, GuiUtil.setConstraints(0, 2, 0, 0, GridBagConstraints.HORIZONTAL, 10, 0, 0, 0)); // Geocoder picker JPanel geocoder = new JPanel(); geocoder.setBorder(componentBorder); geocoder.setLayout(new GridBagLayout()); geocoderTitle = new JLabel(); geocoderTitle.setFont(geocoderTitle.getFont().deriveFont(Font.BOLD)); geocoderTitle.setIcon(new ImageIcon(getClass().getResource("/org/citydb/gui/images/map/magnifier.png"))); geocoderCombo = new JComboBox<>(); for (GeocodingServiceName serviceName : GeocodingServiceName.values()) geocoderCombo.addItem(serviceName); geocoder.add(geocoderTitle, GuiUtil.setConstraints(0, 0, 1, 0, GridBagConstraints.HORIZONTAL, 0, 0, 2, 0)); geocoder.add(geocoderCombo, GuiUtil.setConstraints(0, 1, 1, 0, GridBagConstraints.HORIZONTAL, 10, 0, 0, 0)); // Google maps JPanel googleMaps = new JPanel(); googleMaps.setBorder(componentBorder); googleMaps.setLayout(new GridBagLayout()); googleMapsTitle = new JLabel(); googleMapsTitle.setFont(googleMapsTitle.getFont().deriveFont(Font.BOLD)); googleMapsTitle.setIcon(new ImageIcon(getClass().getResource("/org/citydb/gui/images/map/google_maps.png"))); googleMapsButton = new JButton(); googleMapsButton.setEnabled(Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Action.BROWSE)); googleMaps.add(googleMapsTitle, GuiUtil.setConstraints(0, 0, 0, 0, GridBagConstraints.HORIZONTAL, 0, 0, 2, 0)); googleMaps.add(googleMapsButton, GuiUtil.setConstraints(1, 0, 1, 0, GridBagConstraints.NONE, 0, 5, 0, 0)); // help JPanel help = new JPanel(); help.setBorder(componentBorder); help.setLayout(new GridBagLayout()); helpTitle = new JLabel(); helpTitle.setFont(help.getFont().deriveFont(Font.BOLD)); helpTitle.setIcon(new ImageIcon(getClass().getResource("/org/citydb/gui/images/map/help.png"))); helpTitle.setIconTextGap(5); helpText = new JLabel(); help.add(helpTitle, GuiUtil.setConstraints(0, 0, 1, 0, GridBagConstraints.HORIZONTAL, 0, 0, 2, 0)); help.add(helpText, GuiUtil.setConstraints(0, 1, 0, 0, GridBagConstraints.BOTH, 10, 0, 0, 0)); left.add(bbox, GuiUtil.setConstraints(0, 0, 1, 0, GridBagConstraints.BOTH, 5, 0, 5, 0)); left.add(reverse, GuiUtil.setConstraints(0, 1, 1, 0, GridBagConstraints.BOTH, 5, 0, 5, 0)); left.add(geocoder, GuiUtil.setConstraints(0, 2, 1, 0, GridBagConstraints.BOTH, 5, 0, 5, 0)); left.add(googleMaps, GuiUtil.setConstraints(0, 3, 1, 0, GridBagConstraints.BOTH, 5, 0, 5, 0)); left.add(help, GuiUtil.setConstraints(0, 4, 1, 0, GridBagConstraints.BOTH, 5, 0, 5, 0)); left.add(Box.createVerticalGlue(), GuiUtil.setConstraints(0, 5, 0, 1, GridBagConstraints.VERTICAL, 5, 0, 2, 0)); left.setMinimumSize(left.getPreferredSize()); left.setPreferredSize(left.getMinimumSize()); // actions goButton.addActionListener(e -> { if (searchBox.getSelectedItem() != null) geocode(searchBox.getSelectedItem().toString()); }); searchBox.getEditor().addActionListener(e -> geocode(e.getActionCommand())); searchBox.addActionListener(e -> { if (updateSearchBox && !"comboBoxEdited".equals(e.getActionCommand())) { Object selectedItem = searchBox.getSelectedItem(); if (selectedItem instanceof Location) { Location location = (Location)selectedItem; map.getMapKit().getMainMap().setZoom(1); HashSet<GeoPosition> viewPort = new HashSet<>(2); viewPort.add(location.getViewPort().getSouthWest()); viewPort.add(location.getViewPort().getNorthEast()); map.getMapKit().getMainMap().calculateZoomFrom(viewPort); WaypointType type = location.getLocationType() == LocationType.PRECISE ? WaypointType.PRECISE : WaypointType.APPROXIMATE; map.getWaypointPainter().showWaypoints(new DefaultWaypoint(location.getPosition(), type)); } } }); clearBBox.addActionListener(e -> clearBoundingBox()); KeyAdapter showBBoxAdapter = new KeyAdapter() { public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) showBoundingBox(); } }; minX.addKeyListener(showBBoxAdapter); minY.addKeyListener(showBBoxAdapter); maxX.addKeyListener(showBBoxAdapter); maxY.addKeyListener(showBBoxAdapter); showBBox.addActionListener(e -> showBoundingBox()); copyBBox.addActionListener(e -> copyBoundingBoxToClipboard()); pasteBBox.addActionListener(e -> pasteBoundingBoxFromClipboard()); PopupMenuDecorator popupMenuDecorator = PopupMenuDecorator.getInstance(); popupMenuDecorator.decorate((JComponent)searchBox.getEditor().getEditorComponent(), reverseText); // popup menu final JPopupMenu popupMenu = new JPopupMenu(); bboxPopups = new BBoxPopupMenu[5]; bboxPopups[0] = new BBoxPopupMenu(popupMenuDecorator.decorateAndGet(minX), true); bboxPopups[1] = new BBoxPopupMenu(popupMenuDecorator.decorateAndGet(minY), true); bboxPopups[2] = new BBoxPopupMenu(popupMenuDecorator.decorateAndGet(maxX), true); bboxPopups[3] = new BBoxPopupMenu(popupMenuDecorator.decorateAndGet(maxY), true); bboxPopups[4] = new BBoxPopupMenu(popupMenu, false); Toolkit.getDefaultToolkit().getSystemClipboard().addFlavorListener(e -> { boolean enable = clipboardHandler.containsPossibleBoundingBox(); pasteBBox.setEnabled(enable); for (BBoxPopupMenu bboxPopup : bboxPopups) bboxPopup.paste.setEnabled(enable); }); PropertyChangeListener valueChangedListener = evt -> { if (evt.getPropertyName().equals("value")) { if (evt.getOldValue() instanceof Number && evt.getNewValue() instanceof Number) { String oldValue = LAT_LON_FORMATTER.format(((Number)evt.getOldValue()).doubleValue()); String newValue = LAT_LON_FORMATTER.format(((Number)evt.getNewValue()).doubleValue()); if (oldValue.equals(newValue)) return; } try { minX.commitEdit(); minY.commitEdit(); maxX.commitEdit(); maxY.commitEdit(); GeoPosition southWest = new GeoPosition(((Number)minY.getValue()).doubleValue(), ((Number)minX.getValue()).doubleValue()); GeoPosition northEast = new GeoPosition(((Number)maxY.getValue()).doubleValue(), ((Number)maxX.getValue()).doubleValue()); setEnabledApplyBoundingBox(map.getSelectionPainter().isVisibleOnScreen(southWest, northEast)); } catch (ParseException e) { // } } }; minX.addPropertyChangeListener(valueChangedListener); minY.addPropertyChangeListener(valueChangedListener); maxX.addPropertyChangeListener(valueChangedListener); maxY.addPropertyChangeListener(valueChangedListener); bbox.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { showPopupMenu(e); } public void mouseReleased(MouseEvent e) { showPopupMenu(e); } private void showPopupMenu(MouseEvent e) { if (e.isPopupTrigger()) { popupMenu.show(e.getComponent(), e.getX(), e.getY()); popupMenu.setInvoker(bbox); } } }); addWindowListener(new WindowAdapter() { public void windowClosed(WindowEvent e) { // clear map cache ((AbstractTileFactory)map.getMapKit().getMainMap().getTileFactory()).clearTileCache(); ((AbstractTileFactory)map.getMapKit().getMainMap().getTileFactory()).shutdownTileServicePool(); ((AbstractTileFactory)map.getMapKit().getMiniMap().getTileFactory()).clearTileCache(); ((AbstractTileFactory)map.getMapKit().getMiniMap().getTileFactory()).shutdownTileServicePool(); WindowSize size = config.getGui().getMapWindow().getSize(); Rectangle rect = MapWindow.this.getBounds(); size.setX(rect.x); size.setY(rect.y); size.setWidth(rect.width); size.setHeight(rect.height); } }); applyButton.addActionListener(e -> { double xmin = ((Number)minX.getValue()).doubleValue(); double xmax = ((Number)maxX.getValue()).doubleValue(); double ymin = ((Number)minY.getValue()).doubleValue(); double ymax = ((Number)maxY.getValue()).doubleValue(); final BoundingBox bbox1 = new BoundingBox(); bbox1.getLowerCorner().setX(Math.min(xmin, xmax)); bbox1.getLowerCorner().setY(Math.min(ymin, ymax)); bbox1.getUpperCorner().setX(Math.max(xmin, xmax)); bbox1.getUpperCorner().setY(Math.max(ymin, ymax)); DatabaseSrs wgs84 = null; for (DatabaseSrs srs : config.getProject().getDatabase().getReferenceSystems()) { if (srs.getSrid() == Database.PREDEFINED_SRS.get(PredefinedSrsName.WGS84_2D).getSrid()) { wgs84 = srs; break; } } bbox1.setSrs(wgs84); Thread t = new Thread(() -> listener.setBoundingBox(bbox1)); t.setDaemon(true); t.start(); copyBoundingBoxToClipboard(); dispose(); }); cancelButton.addActionListener(e -> dispose()); geocoderCombo.addItemListener(l -> { if (l.getStateChange() == ItemEvent.SELECTED && geocoderCombo.getSelectedItem() != config.getGui().getMapWindow().getGeocoder()) { try { GeocodingService service = getGeocodingService((GeocodingServiceName) geocoderCombo.getSelectedItem()); Geocoder.getInstance().setGeocodingService(service); } catch (GeocodingServiceException e) { SwingUtilities.invokeLater(() -> { JOptionPane.showMessageDialog(this, e.getMessage(), Language.I18N.getString("map.error.geocoder.title"), JOptionPane.ERROR_MESSAGE); }); geocoderCombo.setSelectedItem(config.getGui().getMapWindow().getGeocoder()); } } }); googleMapsButton.addActionListener(e -> { Rectangle view = map.getMapKit().getMainMap().getViewportBounds(); TileFactory fac = map.getMapKit().getMainMap().getTileFactory(); int zoom = map.getMapKit().getMainMap().getZoom(); GeoPosition centerPoint = fac.pixelToGeo(new Point2D.Double(view.getCenterX(), view.getCenterY()), zoom); GeoPosition southWest = fac.pixelToGeo(new Point2D.Double(view.getMinX(), view.getMaxY()), zoom); GeoPosition northEast = fac.pixelToGeo(new Point2D.Double(view.getMaxX(), view.getMinY()), zoom); final StringBuilder url = new StringBuilder(); url.append("http://maps.google.de/maps?"); if (searchBox.getSelectedItem() instanceof Location) { String query = ((Location)searchBox.getSelectedItem()).getFormattedAddress(); try { url.append("&q=").append(URLEncoder.encode(query, StandardCharsets.UTF_8.displayName())); } catch (UnsupportedEncodingException e1) { // } } url.append("&ll=").append(centerPoint.getLatitude()).append(",").append(centerPoint.getLongitude()); url.append("&spn=").append((northEast.getLatitude() - southWest.getLatitude()) / 2).append(",").append((northEast.getLongitude() - southWest.getLongitude()) / 2); url.append("&sspn=").append(northEast.getLatitude() - southWest.getLatitude()).append(",").append(northEast.getLongitude() - southWest.getLongitude()); url.append("&t=m"); SwingUtilities.invokeLater(() -> { try { Desktop.getDesktop().browse(new URI(url.toString())); } catch (IOException e1) { log.error("Failed to launch default browser."); } catch (URISyntaxException e1) { // } }); }); } public void setBoundingBox(final BoundingBox bbox) { new SwingWorker<ValidationResult, Void>() { protected ValidationResult doInBackground() throws Exception { return validator.validate(bbox); } protected void done() { try { switch (get()) { case CANCEL: dispose(); break; case SKIP: case OUT_OF_RANGE: case NO_AREA: clearBoundingBox(); break; case INVISIBLE: clearBoundingBox(); indicateInvisibleBoundingBox(bbox); break; default: minX.setValue(bbox.getLowerCorner().getX()); minY.setValue(bbox.getLowerCorner().getY()); maxX.setValue(bbox.getUpperCorner().getX()); maxY.setValue(bbox.getUpperCorner().getY()); showBoundingBox(); } } catch (InterruptedException | ExecutionException e) { // } } }.execute(); } public boolean isBoundingBoxVisible(BoundingBox bbox) { GeoPosition southWest = new GeoPosition(bbox.getLowerCorner().getY(), bbox.getLowerCorner().getX()); GeoPosition northEast = new GeoPosition(bbox.getUpperCorner().getY(), bbox.getUpperCorner().getX()); return map.getSelectionPainter().isVisibleOnScreen(southWest, northEast); } private void copyBoundingBoxToClipboard() { try { minX.commitEdit(); minY.commitEdit(); maxX.commitEdit(); maxY.commitEdit(); BoundingBox bbox = new BoundingBox(); bbox.getLowerCorner().setX(minX.isEditValid() && minX.getValue() != null ? ((Number)minX.getValue()).doubleValue() : null); bbox.getLowerCorner().setY(minY.isEditValid() && minY.getValue() != null ? ((Number)minY.getValue()).doubleValue() : null); bbox.getUpperCorner().setX(maxX.isEditValid() && maxX.getValue() != null ? ((Number)maxX.getValue()).doubleValue() : null); bbox.getUpperCorner().setY(maxY.isEditValid() && maxY.getValue() != null ? ((Number)maxY.getValue()).doubleValue() : null); for (DatabaseSrs srs : config.getProject().getDatabase().getReferenceSystems()) { if (srs.getSrid() == Database.PREDEFINED_SRS.get(PredefinedSrsName.WGS84_2D).getSrid()) { bbox.setSrs(srs); break; } } clipboardHandler.putBoundingBox(bbox); } catch (ParseException e) { // } } private void pasteBoundingBoxFromClipboard() { setBoundingBox(clipboardHandler.getBoundingBox()); } private void showBoundingBox() { try { minX.commitEdit(); minY.commitEdit(); maxX.commitEdit(); maxY.commitEdit(); GeoPosition southWest = new GeoPosition(((Number)minY.getValue()).doubleValue(), ((Number)minX.getValue()).doubleValue()); GeoPosition northEast = new GeoPosition(((Number)maxY.getValue()).doubleValue(), ((Number)maxX.getValue()).doubleValue()); if (map.getSelectionPainter().setBoundingBox(southWest, northEast)) { HashSet<GeoPosition> positions = new HashSet<>(); positions.add(southWest); positions.add(northEast); map.getMapKit().setZoom(1); map.getMapKit().getMainMap().calculateZoomFrom(positions); } else map.getSelectionPainter().clearBoundingBox(); } catch (ParseException e) { // } } private void indicateInvisibleBoundingBox(BoundingBox bbox) { double x = bbox.getLowerCorner().getX() + (bbox.getUpperCorner().getX() - bbox.getLowerCorner().getX()) / 2; double y = bbox.getLowerCorner().getY() + (bbox.getUpperCorner().getY() - bbox.getLowerCorner().getY()) / 2; GeoPosition pos = new GeoPosition(y, x); map.getWaypointPainter().clearWaypoints(); map.getWaypointPainter().showWaypoints(new DefaultWaypoint(pos, WaypointType.PRECISE)); map.getMapKit().setZoom(map.getMapKit().getMainMap().getTileFactory().getInfo().getMinimumZoomLevel()); map.getMapKit().setCenterPosition(pos); } private void clearBoundingBox() { map.getSelectionPainter().clearBoundingBox(); minX.setValue(null); maxX.setValue(null); minY.setValue(null); maxY.setValue(null); setEnabledApplyBoundingBox(false); } private void setEnabledApplyBoundingBox(boolean enable) { applyButton.setEnabled(enable); copyBBox.setEnabled(enable); for (BBoxPopupMenu bboxPopup : bboxPopups) bboxPopup.copy.setEnabled(enable); } private void geocode(final String address) { searchResult.setIcon(loadIcon); searchResult.setText(""); searchResult.repaint(); final long time = System.currentTimeMillis(); new SwingWorker<GeocoderResult, Void>() { protected GeocoderResult doInBackground() throws Exception { return Geocoder.getInstance().geocode(address); } protected void done() { try { GeocoderResult result = get(); searchBox.removeAllItems(); if (result.isSetLocations()) { for (Location location : result.getLocations()) searchBox.addItem(location); searchBox.setSelectedItem(result.getLocations().get(0)); } String text = Language.I18N.getString("map.geocoder.search.result"); Object[] args = new Object[]{result.getLocations().size()}; String resultMsg = MessageFormat.format(text, args) + " (" + ((System.currentTimeMillis() - time) / 1000.0) + " s)"; searchResult.setText(resultMsg); } catch (InterruptedException | ExecutionException e) { if (e.getCause() instanceof GeocodingServiceException) { GeocodingServiceException exception = (GeocodingServiceException) e.getCause(); searchResult.setText("The geocoder failed due to an error. Check the console log."); log.error("The geocoder failed due to an error."); for (String message : exception.getMessages()) log.error("Cause: " + message); } else { log.error("An error occured while calling the geocoding service."); log.error("Caused by: " + e.getMessage()); } } finally { searchResult.setIcon(null); } } }.execute(); } private GeocodingService getGeocodingService(GeocodingServiceName serviceName) throws GeocodingServiceException { GeocodingService service = null; if (serviceName == GeocodingServiceName.OSM_NOMINATIM) service = new OSMGeocoder(); else if (serviceName == GeocodingServiceName.GOOGLE_GEOCODING_API) { if (config.getProject().getGlobal().getApiKeys().isSetGoogleGeocoding()) service = new GoogleGeocoder(config.getProject().getGlobal().getApiKeys().getGoogleGeocoding()); else { Logger.getInstance().error("Failed to initialize geocoder '" + serviceName.toString() + "' due to a missing API key."); throw new GeocodingServiceException(MessageFormat.format(Language.I18N.getString("map.error.geocoder.apiKey"), serviceName)); } } if (service != null) config.getGui().getMapWindow().setGeocoder(serviceName); return service; } private void setSizeOnScreen() { WindowSize size = config.getGui().getMapWindow().getSize(); Integer x = size.getX(); Integer y = size.getY(); Integer width = size.getWidth(); Integer height = size.getHeight(); // create default values for main window if (x == null || y == null || width == null || height == null) { x = mainFrame.getLocation().x + 10; y = mainFrame.getLocation().y + 10; width = 1024; height = 768; Toolkit t = Toolkit.getDefaultToolkit(); Insets frame_insets = t.getScreenInsets(mainFrame.getGraphicsConfiguration()); int frame_insets_x = frame_insets.left + frame_insets.right; int frame_insets_y = frame_insets.bottom + frame_insets.top; Rectangle bounds = mainFrame.getGraphicsConfiguration().getBounds(); if (!bounds.contains(x, y, width + frame_insets_x, height + frame_insets_y)) { // check width if (x + width + frame_insets_x > bounds.width || y + height + frame_insets_y > bounds.height) { x = frame_insets.left; y = frame_insets.top; if (width + frame_insets_x > bounds.width) width = bounds.width - frame_insets_x; if (height + frame_insets_y > bounds.height) height = bounds.height - frame_insets_y; } } } setLocation(x, y); setSize(new Dimension(width, height)); } private void doTranslation() { setTitle(Language.I18N.getString("map.window.title")); applyButton.setText(Language.I18N.getString("common.button.apply")); cancelButton.setText(Language.I18N.getString("common.button.cancel")); goButton.setText(Language.I18N.getString("map.button.go")); bboxTitel.setText(Language.I18N.getString("map.boundingBox.label")); showBBox.setText(Language.I18N.getString("map.boundingBox.show.button")); showBBox.setToolTipText(Language.I18N.getString("map.boundingBox.show.tooltip")); clearBBox.setText(Language.I18N.getString("map.boundingBox.clear.button")); clearBBox.setToolTipText(Language.I18N.getString("map.boundingBox.clear.tooltip")); copyBBox.setToolTipText(Language.I18N.getString("common.tooltip.boundingBox.copy")); pasteBBox.setToolTipText(Language.I18N.getString("common.tooltip.boundingBox.paste")); reverseTitle.setText(Language.I18N.getString("map.reverseGeocoder.label")); reverseInfo.setText("<html>" + Language.I18N.getString("map.reverseGeocoder.hint.label") + "</html>"); geocoderTitle.setText(Language.I18N.getString("map.geocoder.label")); helpTitle.setText(Language.I18N.getString("map.help.label")); helpText.setText("<html>" + Language.I18N.getString("map.help.hint") + "</html>"); googleMapsButton.setText(Language.I18N.getString("map.google.label")); map.doTranslation(); for (BBoxPopupMenu bboxPopup : bboxPopups) bboxPopup.doTranslation(); } @Override public void handleEvent(Event event) throws Exception { if (event.getEventType() == EventType.SWITCH_LOCALE) { doTranslation(); } else if (event.getEventType() == MapEvents.BOUNDING_BOX_SELECTION) { BoundingBoxSelectionEvent e = (BoundingBoxSelectionEvent)event; final GeoPosition[] bbox = e.getBoundingBox(); SwingUtilities.invokeLater(() -> { // avoid property listener on text fields to be fired // before setting the last value maxY.setValue(null); minX.setValue(bbox[0].getLatitude()); minY.setValue(bbox[0].getLongitude()); maxX.setValue(bbox[1].getLatitude()); maxY.setValue(bbox[1].getLongitude()); }); } else if (event.getEventType() == MapEvents.MAP_BOUNDS) { MapBoundsSelectionEvent e = (MapBoundsSelectionEvent)event; final GeoPosition[] bbox = e.getBoundingBox(); SwingUtilities.invokeLater(() -> { // avoid property listener on text fields to be fired // before setting the last value maxY.setValue(null); minX.setValue(bbox[0].getLongitude()); minY.setValue(bbox[0].getLatitude()); maxX.setValue(bbox[1].getLongitude()); maxY.setValue(bbox[1].getLatitude()); map.getSelectionPainter().setBoundingBox(bbox[0], bbox[1]); }); } else if (event.getEventType() == MapEvents.REVERSE_GEOCODER) { ReverseGeocoderEvent e = (ReverseGeocoderEvent)event; SwingUtilities.invokeLater(() -> { if (e.getStatus() == ReverseGeocoderStatus.SEARCHING) { reverseSearchProgress.setIcon(loadIcon); } else if (e.getStatus() == ReverseGeocoderStatus.RESULT) { Location location = e.getLocation(); reverseText.setText(location.getFormattedAddress()); reverseText.setVisible(true); reverseInfo.setVisible(false); reverseSearchProgress.setIcon(null); location.setFormattedAddress(location.getPosition().getLatitude() + ", " + location.getPosition().getLongitude()); updateSearchBox = false; searchBox.setSelectedItem(location); updateSearchBox = true; } else { reverseText.setVisible(false); reverseInfo.setVisible(true); reverseSearchProgress.setIcon(null); if (e.getStatus() == ReverseGeocoderStatus.ERROR) { reverseInfo.setText("<html>The geocoder failed due to an error. Check the console log.</html>"); GeocodingServiceException exception = e.getException(); log.error("The geocoder failed due to an error."); for (String message : exception.getMessages()) log.error("Cause: " + message); } else reverseInfo.setText("<html>No address found at this location.</html>"); } }); } } private final class BBoxPopupMenu extends JPopupMenu { private JMenuItem copy; private JMenuItem paste; BBoxPopupMenu(JPopupMenu popupMenu, boolean addSeparator) { copy = new JMenuItem(); paste = new JMenuItem(); copy.setEnabled(false); paste.setEnabled(clipboardHandler.containsPossibleBoundingBox()); if (addSeparator) popupMenu.addSeparator(); popupMenu.add(copy); popupMenu.add(paste); copy.addActionListener(e -> copyBoundingBoxToClipboard()); paste.addActionListener(e -> pasteBoundingBoxFromClipboard()); } private void doTranslation() { copy.setText(Language.I18N.getString("common.popup.boundingBox.copy")); paste.setText(Language.I18N.getString("common.popup.boundingBox.paste")); } } }