001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.preferences.projection; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.BorderLayout; 007import java.awt.GridBagLayout; 008import java.awt.Insets; 009import java.awt.event.ActionListener; 010import java.util.Arrays; 011import java.util.Collection; 012import java.util.Collections; 013import java.util.List; 014 015import javax.swing.JButton; 016import javax.swing.JComponent; 017import javax.swing.JLabel; 018import javax.swing.JPanel; 019import javax.swing.plaf.basic.BasicComboBoxEditor; 020 021import org.openstreetmap.josm.data.projection.CustomProjection; 022import org.openstreetmap.josm.data.projection.Projection; 023import org.openstreetmap.josm.data.projection.ProjectionConfigurationException; 024import org.openstreetmap.josm.data.projection.Projections; 025import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem; 026import org.openstreetmap.josm.gui.ExtendedDialog; 027import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator; 028import org.openstreetmap.josm.gui.widgets.HistoryComboBox; 029import org.openstreetmap.josm.gui.widgets.HtmlPanel; 030import org.openstreetmap.josm.gui.widgets.JosmTextField; 031import org.openstreetmap.josm.spi.preferences.Config; 032import org.openstreetmap.josm.tools.GBC; 033import org.openstreetmap.josm.tools.ImageProvider; 034import org.openstreetmap.josm.tools.Logging; 035 036/** 037 * ProjectionChoice where a CRS can be defined using various parameters. 038 * <p> 039 * The configuration string mimics the syntax of the PROJ.4 project and should 040 * be mostly compatible. 041 * @see CustomProjection 042 */ 043public class CustomProjectionChoice extends AbstractProjectionChoice implements SubPrefsOptions { 044 045 private String pref; 046 047 /** 048 * Constructs a new {@code CustomProjectionChoice}. 049 */ 050 public CustomProjectionChoice() { 051 super(tr("Custom Projection"), /* NO-ICON */ "core:custom"); 052 } 053 054 private static class PreferencePanel extends JPanel { 055 056 public JosmTextField input; 057 private HistoryComboBox cbInput; 058 059 PreferencePanel(String initialText, ActionListener listener) { 060 build(initialText, listener); 061 } 062 063 private void build(String initialText, final ActionListener listener) { 064 input = new JosmTextField(30); 065 cbInput = new HistoryComboBox(); 066 cbInput.setPrototypeDisplayValue(new AutoCompletionItem("xxxx")); 067 cbInput.setEditor(new BasicComboBoxEditor() { 068 @Override 069 protected JosmTextField createEditorComponent() { 070 return input; 071 } 072 }); 073 List<String> samples = Arrays.asList( 074 "+proj=lonlat +ellps=WGS84 +datum=WGS84 +bounds=-180,-90,180,90", 075 "+proj=tmerc +lat_0=0 +lon_0=9 +k_0=1 +x_0=3500000 +y_0=0 +ellps=bessel +nadgrids=BETA2007.gsb"); 076 cbInput.setPossibleItemsTopDown(Config.getPref().getList("projection.custom.value.history", samples)); 077 cbInput.setText(initialText == null ? "" : initialText); 078 079 final HtmlPanel errorsPanel = new HtmlPanel(); 080 errorsPanel.setVisible(false); 081 final JLabel valStatus = new JLabel(); 082 valStatus.setVisible(false); 083 084 final AbstractTextComponentValidator val = new AbstractTextComponentValidator(input, false, false, false) { 085 086 private String error; 087 088 @Override 089 public void validate() { 090 if (!isValid()) { 091 feedbackInvalid(tr("Invalid projection configuration: {0}", error)); 092 } else { 093 feedbackValid(tr("Projection configuration is valid.")); 094 } 095 listener.actionPerformed(null); 096 } 097 098 @Override 099 public boolean isValid() { 100 try { 101 CustomProjection test = new CustomProjection(); 102 test.update(input.getText()); 103 } catch (ProjectionConfigurationException ex) { 104 Logging.warn(ex); 105 error = ex.getMessage(); 106 valStatus.setIcon(ImageProvider.get("data", "error")); 107 valStatus.setVisible(true); 108 errorsPanel.setText(error); 109 errorsPanel.setVisible(true); 110 return false; 111 } 112 errorsPanel.setVisible(false); 113 valStatus.setIcon(ImageProvider.get("misc", "green_check")); 114 valStatus.setVisible(true); 115 return true; 116 } 117 }; 118 119 JButton btnCheck = new JButton(tr("Validate")); 120 btnCheck.addActionListener(e -> val.validate()); 121 btnCheck.setLayout(new BorderLayout()); 122 btnCheck.setMargin(new Insets(-1, 0, -1, 0)); 123 124 JButton btnInfo = new JButton(tr("Parameter information...")); 125 btnInfo.addActionListener(e -> { 126 CustomProjectionChoice.ParameterInfoDialog dlg = new CustomProjectionChoice.ParameterInfoDialog(); 127 dlg.showDialog(); 128 dlg.toFront(); 129 }); 130 131 this.setLayout(new GridBagLayout()); 132 JPanel p2 = new JPanel(new GridBagLayout()); 133 p2.add(cbInput, GBC.std().fill(GBC.HORIZONTAL).insets(0, 20, 5, 5)); 134 p2.add(btnCheck, GBC.eol().insets(0, 20, 0, 5)); 135 this.add(p2, GBC.eol().fill(GBC.HORIZONTAL)); 136 p2 = new JPanel(new GridBagLayout()); 137 p2.add(valStatus, GBC.std().anchor(GBC.WEST).weight(0.0001, 0)); 138 p2.add(errorsPanel, GBC.eol().fill(GBC.HORIZONTAL)); 139 this.add(p2, GBC.eol().fill(GBC.HORIZONTAL)); 140 p2 = new JPanel(new GridBagLayout()); 141 p2.add(btnInfo, GBC.std().insets(0, 20, 0, 0)); 142 p2.add(GBC.glue(1, 0), GBC.eol().fill(GBC.HORIZONTAL)); 143 this.add(p2, GBC.eol().fill(GBC.HORIZONTAL)); 144 } 145 146 public void rememberHistory() { 147 cbInput.addCurrentItemToHistory(); 148 Config.getPref().putList("projection.custom.value.history", cbInput.getHistory()); 149 } 150 } 151 152 public static class ParameterInfoDialog extends ExtendedDialog { 153 154 /** 155 * Constructs a new {@code ParameterInfoDialog}. 156 */ 157 public ParameterInfoDialog() { 158 super(null, tr("Parameter information"), new String[] {tr("Close")}, false); 159 setContent(build()); 160 } 161 162 private static JComponent build() { 163 StringBuilder s = new StringBuilder(1024); 164 s.append("<b>+proj=...</b> - <i>").append(tr("Projection name")) 165 .append("</i><br> ").append(tr("Supported values:")).append(' ') 166 .append(Projections.listProjs()) 167 .append("<br><b>+lat_0=..., +lat_1=..., +lat_2=...</b> - <i>").append(tr("Projection parameters")) 168 .append("</i><br><b>+x_0=..., +y_0=...</b> - <i>").append(tr("False easting and false northing")) 169 .append("</i><br><b>+lon_0=...</b> - <i>").append(tr("Central meridian")) 170 .append("</i><br><b>+k_0=...</b> - <i>").append(tr("Scaling factor")) 171 .append("</i><br><b>+ellps=...</b> - <i>").append(tr("Ellipsoid name")) 172 .append("</i><br> ").append(tr("Supported values:")).append(' ') 173 .append(Projections.listEllipsoids()) 174 .append("<br><b>+a=..., +b=..., +rf=..., +f=..., +es=...</b> - <i>").append(tr("Ellipsoid parameters")) 175 .append("</i><br><b>+datum=...</b> - <i>").append(tr("Datum name")) 176 .append("</i><br> ").append(tr("Supported values:")).append(' ') 177 .append(Projections.listDatums()) 178 .append("<br><b>+towgs84=...</b> - <i>").append(tr("3 or 7 term datum transform parameters")) 179 .append("</i><br><b>+nadgrids=...</b> - <i>").append(tr("NTv2 grid file")) 180 .append("</i><br> ").append(tr("Built-in:")).append(' ') 181 .append(Projections.listNadgrids()) 182 .append("<br><b>+bounds=</b>minlon,minlat,maxlon,maxlat - <i>").append(tr("Projection bounds (in degrees)")) 183 .append("</i><br><b>+wmssrs=</b>EPSG:123456 - <i>").append(tr("Sets the SRS=... parameter in the WMS request")) 184 .append("</i><br>"); 185 186 return new HtmlPanel(s.toString()); 187 } 188 } 189 190 @Override 191 public void setPreferences(Collection<String> args) { 192 if (args != null && !args.isEmpty()) { 193 pref = args.iterator().next(); 194 } 195 } 196 197 @Override 198 public Projection getProjection() { 199 return new CustomProjection(pref); 200 } 201 202 @Override 203 public String getCurrentCode() { 204 // not needed - getProjection() is overridden 205 throw new UnsupportedOperationException(); 206 } 207 208 @Override 209 public String getProjectionName() { 210 // not needed - getProjection() is overridden 211 throw new UnsupportedOperationException(); 212 } 213 214 @Override 215 public JPanel getPreferencePanel(ActionListener listener) { 216 return new PreferencePanel(pref, listener); 217 } 218 219 @Override 220 public Collection<String> getPreferences(JPanel panel) { 221 if (!(panel instanceof PreferencePanel)) { 222 throw new IllegalArgumentException("Unsupported panel: "+panel); 223 } 224 PreferencePanel prefPanel = (PreferencePanel) panel; 225 String pref = prefPanel.input.getText(); 226 prefPanel.rememberHistory(); 227 return Collections.singleton(pref); 228 } 229 230 @Override 231 public String[] allCodes() { 232 return new String[0]; 233 } 234 235 @Override 236 public Collection<String> getPreferencesFromCode(String code) { 237 return null; 238 } 239 240 @Override 241 public boolean showProjectionCode() { 242 return false; 243 } 244 245 @Override 246 public boolean showProjectionName() { 247 return false; 248 } 249}