/* * uDig - User Friendly Desktop Internet GIS client * http://udig.refractions.net * (C) 2004, Refractions Research Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * */ package org.geotools.swt.control; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import java.util.TreeSet; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.core.runtime.CoreException; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.ListViewer; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ModifyEvent; import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.List; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.TabFolder; import org.eclipse.swt.widgets.TabItem; import org.eclipse.swt.widgets.Text; import org.geotools.referencing.CRS; import org.geotools.referencing.ReferencingFactoryFinder; import org.geotools.referencing.crs.DefaultGeographicCRS; import org.geotools.swt.utils.Messages; import org.opengis.metadata.Identifier; import org.opengis.referencing.FactoryException; import org.opengis.referencing.ReferenceIdentifier; import org.opengis.referencing.crs.CRSAuthorityFactory; import org.opengis.referencing.crs.CoordinateReferenceSystem; /** * Creates a Control for choosing a Coordinate Reference System. * * @author jeichar * @since 0.6.0 * * * @source $URL: http://svn.osgeo.org/geotools/trunk/modules/unsupported/swt/src/main/java/org/geotools/swt/control/CRSChooser.java $ */ public class CRSChooser { private static final String WKT_ID = "WKT"; //$NON-NLS-1$ private static final String ALIASES_ID = "ALIASES"; //$NON-NLS-1$ private static final String LAST_ID = "LAST_ID"; //$NON-NLS-1$ private static final String NAME_ID = "NAME_ID"; //$NON-NLS-1$ private static final String CUSTOM_ID = "CRS.Custom.Services"; //$NON-NLS-1$ private static final Controller DEFAULT = new Controller(){ public void handleClose() { } public void handleOk() { } }; ListViewer codesList; Text searchText; Text wktText; Text keywordsText; CoordinateReferenceSystem selectedCRS; Matcher matcher; private TabFolder folder; private Controller parentPage; private HashMap<String, String> crsCodeMap; private CoordinateReferenceSystem sourceCRS; public CRSChooser( Controller parentPage ) { matcher = Pattern.compile(".*?\\(([^(]*)\\)$").matcher(""); //$NON-NLS-1$ //$NON-NLS-2$ this.parentPage = parentPage; } public CRSChooser() { this(DEFAULT); } private Control createCustomCRSControl( Composite parent ) { Composite composite = new Composite(parent, SWT.NONE); GridLayout layout = new GridLayout(2, false); composite.setLayout(layout); GridData gridData = new GridData(); Label keywordsLabel = new Label(composite, SWT.NONE); keywordsLabel.setText(Messages.getString("CRSChooser_keywordsLabel")); keywordsLabel.setLayoutData(gridData); keywordsLabel.setToolTipText(Messages.getString("CRSChooser_tooltip")); gridData = new GridData(SWT.FILL, SWT.NONE, true, false); keywordsText = new Text(composite, SWT.SINGLE | SWT.BORDER); keywordsText.setLayoutData(gridData); keywordsText.setToolTipText(Messages.getString("CRSChooser_tooltip")); gridData = new GridData(SWT.FILL, SWT.NONE, true, false); gridData.horizontalSpan = 2; Label editorLabel = new Label(composite, SWT.NONE); editorLabel.setText(Messages.getString("CRSChooser_label_crsWKT")); editorLabel.setLayoutData(gridData); gridData = new GridData(SWT.FILL, SWT.FILL, true, true); gridData.horizontalSpan = 2; wktText = new Text(composite, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); if (selectedCRS != null) wktText.setText(selectedCRS.toWKT()); wktText.setLayoutData(gridData); wktText.addModifyListener(new ModifyListener(){ public void modifyText( ModifyEvent e ) { if (!keywordsText.isEnabled()) keywordsText.setEnabled(true); } }); searchText.setFocus(); return composite; } private Control createStandardCRSControl( Composite parent ) { Composite composite = new Composite(parent, SWT.NONE); GridLayout layout = new GridLayout(); composite.setLayout(layout); GridData gridData = new GridData(); Label codesLabel = new Label(composite, SWT.NONE); codesLabel.setText(Messages.getString("CRSChooser_label_crs")); codesLabel.setLayoutData(gridData); gridData = new GridData(SWT.FILL, SWT.FILL, false, false); searchText = new Text(composite, SWT.SINGLE | SWT.BORDER | SWT.SEARCH | SWT.CANCEL); searchText.setLayoutData(gridData); searchText.addModifyListener(new ModifyListener(){ public void modifyText( ModifyEvent e ) { fillCodesList(); } }); searchText.addListener(SWT.KeyUp, new Listener(){ public void handleEvent( Event event ) { if (event.keyCode == SWT.ARROW_DOWN) { codesList.getControl().setFocus(); } } }); gridData = new GridData(400, 300); codesList = new ListViewer(composite); codesList.setContentProvider(new ArrayContentProvider()); codesList.setLabelProvider(new LabelProvider()); codesList.addSelectionChangedListener(new ISelectionChangedListener(){ public void selectionChanged( SelectionChangedEvent event ) { selectedCRS = null; String crsCode = (String) ((IStructuredSelection) codesList.getSelection()).getFirstElement(); if (crsCode == null) return; matcher.reset(crsCode); if (matcher.matches()) { selectedCRS = createCRS(matcher.group(1)); if (selectedCRS != null && wktText != null) { wktText.setEditable(true); String wkt = null; try { wkt = selectedCRS.toWKT(); } catch (Exception e) { /* * if unable to generate WKT, just return the * string and make the text area non editable. */ wkt = selectedCRS.toString(); wktText.setEditable(false); } wktText.setText(wkt); Preferences node = findNode(matcher.group(1)); if (node != null) { Preferences kn = node.node(ALIASES_ID); try { String[] keywords = kn.keys(); if (keywords.length > 0) { StringBuffer buffer = new StringBuffer(); for( String string : keywords ) { buffer.append(", "); //$NON-NLS-1$ buffer.append(string); } buffer.delete(0, 2); keywordsText.setText(buffer.toString()); } } catch (BackingStoreException e) { ExceptionMonitor.show(wktText.getShell(), e); } } else { keywordsText.setText(""); //$NON-NLS-1$ } } } } }); codesList.addDoubleClickListener(new IDoubleClickListener(){ public void doubleClick( DoubleClickEvent event ) { parentPage.handleOk(); parentPage.handleClose(); } }); codesList.getControl().setLayoutData(gridData); /* * fillCodesList() by itself resizes the Preferences Page but in the paintlistener it * flickers the window */ fillCodesList(); searchText.setFocus(); return composite; } public void setFocus() { searchText.setFocus(); } /** * Creates the CRS PreferencePage root control with a CRS already selected * * @param parent PreferencePage for this chooser * @param crs current CRS for the associated map * @return control for the PreferencePage */ public Control createControl( Composite parent, CoordinateReferenceSystem crs ) { Control control = createControl(parent); selectedCRS = crs; gotoCRS(selectedCRS); return control; } public void clearSearch() { searchText.setText(""); //$NON-NLS-1$ } /** * Takes in a CRS, finds it in the list and highlights it * * @param crs */ @SuppressWarnings("unchecked") public void gotoCRS( CoordinateReferenceSystem crs ) { if (crs != null) { final List list = codesList.getList(); Set<Identifier> identifiers = new HashSet<Identifier>(crs.getIdentifiers()); final Set<Integer> candidates = new HashSet<Integer>(); for( int i = 0; i < list.getItemCount(); i++ ) { for( Identifier identifier : identifiers ) { final String item = list.getItem(i); if (sameEPSG(crs, identifier, item) || exactMatch(crs, identifier, item)) { codesList.setSelection(new StructuredSelection(item), false); list.setTopIndex(i); return; } if (isMatch(crs, identifier, item)) { candidates.add(i); } } } if (candidates.isEmpty()) { java.util.List<String> input = (java.util.List<String>) codesList.getInput(); String sourceCRSName = crs.getName().toString(); sourceCRS = crs; input.add(0, sourceCRSName); codesList.setInput(input); codesList.setSelection(new StructuredSelection(sourceCRSName), false); list.setTopIndex(0); try { String toWKT = crs.toWKT(); wktText.setText(toWKT); } catch (RuntimeException e) { ExceptionMonitor.show(wktText.getShell(), e, crs.toString() + " cannot be formatted as WKT"); //$NON-NLS-1$ wktText.setText(Messages.getString("CRSChooser_unknownWKT")); } } else { Integer next = candidates.iterator().next(); codesList.setSelection(new StructuredSelection(list.getItem(next)), false); list.setTopIndex(next); } } } private boolean exactMatch( CoordinateReferenceSystem crs, Identifier identifier, String item ) { return (crs == DefaultGeographicCRS.WGS84 && item.equals("WGS 84 (4326)")) || //$NON-NLS-1$ item.equalsIgnoreCase(identifier.toString()) || isInCodeMap(identifier, item); } private boolean isInCodeMap( Identifier identifier, String item ) { String name = crsCodeMap.get(identifier.getCode()); if (name == null) return false; else return name.equals(item); } private boolean sameEPSG( CoordinateReferenceSystem crs, Identifier identifier, String item ) { String toString = identifier.toString(); return toString.contains("EPSG:") && item.contains(toString); //$NON-NLS-1$ } private boolean isMatch( CoordinateReferenceSystem crs, Identifier identifier, String item ) { return (crs == DefaultGeographicCRS.WGS84 && item.contains("4326")) || item.contains(identifier.toString()); //$NON-NLS-1$ } /** * Creates the CRS PreferencePage root control with no CRS selected * * @param parent PreferencePage for this chooser * @return control for the PreferencePage */ public Control createControl( Composite parent ) { GridData gridData = null; gridData = new GridData(SWT.FILL, SWT.FILL, true, true); folder = new TabFolder(parent, SWT.NONE); folder.setLayoutData(gridData); TabItem standard = new TabItem(folder, SWT.NONE); standard.setText(Messages.getString("CRSChooser_tab_standardCRS")); Control stdCRS = createStandardCRSControl(folder); standard.setControl(stdCRS); TabItem custom = new TabItem(folder, SWT.NONE); custom.setText(Messages.getString("CRSChooser_tab_customCRS")); Control cstCRS = createCustomCRSControl(folder); custom.setControl(cstCRS); return folder; } /** * checks if all keywords in filter array are in input * * @param input test string * @param filter array of keywords * @return true, if all keywords in filter are in the input, false otherwise */ protected boolean matchesFilter( String input, String[] filter ) { for( String match : filter ) { if (!input.contains(match)) return false; } return true; } /** * filters all CRS Names from all available CRS authorities * * @param filter array of keywords * @return Set of CRS Names which contain all the filter keywords */ protected Set<String> filterCRSNames( String[] filter ) { crsCodeMap = new HashMap<String, String>(); Set<String> descriptions = new TreeSet<String>(); for( Object object : ReferencingFactoryFinder.getCRSAuthorityFactories(null) ) { CRSAuthorityFactory factory = (CRSAuthorityFactory) object; try { Set<String> codes = factory.getAuthorityCodes(CoordinateReferenceSystem.class); for( Object codeObj : codes ) { String code = (String) codeObj; String description; try { description = factory.getDescriptionText(code).toString(); } catch (Exception e1) { description = Messages.getString("CRSChooser_unnamed"); } description += " (" + code + ")"; //$NON-NLS-1$ //$NON-NLS-2$ crsCodeMap.put(code, description); if (matchesFilter(description.toUpperCase(), filter)) { descriptions.add(description); } } } catch (FactoryException e) { ExceptionMonitor.show(wktText.getShell(), e, "CRS Authority:" + e.getMessage()); } } return descriptions; } /** * populates the codes list with a filtered list of CRS names */ protected void fillCodesList() { String[] searchParms = searchText.getText().toUpperCase().split(" "); //$NON-NLS-1$ Set<String> descriptions = filterCRSNames(searchParms); descriptions = filterCustomCRSs(descriptions, searchParms); java.util.List<String> list = new ArrayList<String>(descriptions); codesList.setInput(list); if (list != null && !list.isEmpty()) { codesList.setSelection(new StructuredSelection(list.get(0))); } else { codesList.setSelection(new StructuredSelection()); // System.out.println( "skipped"); } } private Set<String> filterCustomCRSs( Set<String> descriptions, String[] searchParms ) { try { Preferences root = Preferences.userRoot(); Preferences node = root.node(CUSTOM_ID); for( String id : node.childrenNames() ) { Preferences child = node.node(id); String string = child.get(NAME_ID, null); if (string != null && matchesFilter(string.toUpperCase(), searchParms)) { descriptions.add(string); continue; } Preferences aliases = child.node(ALIASES_ID); for( String alias : aliases.keys() ) { if (matchesFilter(alias.toUpperCase(), searchParms)) { descriptions.add(string); continue; } } } } catch (Exception e) { ExceptionMonitor.show(wktText.getShell(), e); } return descriptions; } /** * creates a CRS from a code when the appropriate CRSAuthorityFactory is unknown * * @param code CRS code * @return CRS object from appropriate authority, or null if the appropriate factory cannot be * determined */ protected CoordinateReferenceSystem createCRS( String code ) { if (code == null) return null; for( Object object : ReferencingFactoryFinder.getCRSAuthorityFactories(null) ) { CRSAuthorityFactory factory = (CRSAuthorityFactory) object; try { return (CoordinateReferenceSystem) factory.createObject(code); } catch (FactoryException e2) { // then we have the wrong factory // is there a better way to do this? } catch (Exception e) { ExceptionMonitor.show(wktText.getShell(), e, "Error creating CRS object, trying more..."); } } try { Preferences child = findNode(code); if (child != null) { String wkt = child.get(WKT_ID, null); if (wkt != null) { try { return ReferencingFactoryFinder.getCRSFactory(null).createFromWKT(wkt); } catch (Exception e) { ExceptionMonitor.show(wktText.getShell(), e); child.removeNode(); } } } } catch (Exception e) { ExceptionMonitor.show(wktText.getShell(), e); } return null; // should throw an exception? } private Preferences findNode( String code ) { try { Preferences root = Preferences.userRoot(); Preferences node = root.node(CUSTOM_ID); if (node.nodeExists(code)) { return node.node(code); } for( String id : node.childrenNames() ) { Preferences child = node.node(id); String name = child.get(NAME_ID, null); if (name != null && matchesFilter(name, new String[]{code})) { return child; } } return null; } catch (BackingStoreException e) { ExceptionMonitor.show(wktText.getShell(), e); return null; } } /** * returns the selected CRS * * @return selected CRS */ public CoordinateReferenceSystem getCRS() { if (folder == null) return selectedCRS; if (folder.getSelectionIndex() == 1) { try { String text = wktText.getText(); CoordinateReferenceSystem createdCRS = ReferencingFactoryFinder.getCRSFactory(null).createFromWKT(text); if (keywordsText.getText().trim().length() > 0) { Preferences node = findNode(createdCRS.getName().getCode()); if (node != null) { Preferences kn = node.node(ALIASES_ID); String[] keywords = keywordsText.getText().split(","); //$NON-NLS-1$ kn.clear(); for( String string : keywords ) { string = string.trim().toUpperCase(); if (string.length() > 0) kn.put(string, string); } kn.flush(); } else { CoordinateReferenceSystem found = createCRS(createdCRS.getName().getCode()); if (found != null && CRS.findMathTransform(found, createdCRS, true).isIdentity()) { saveKeywords(found); return found; } Set<Identifier> identifiers = new HashSet<Identifier>(createdCRS.getIdentifiers()); for( Identifier identifier : identifiers ) { found = createCRS(identifier.toString()); if (found != null && CRS.findMathTransform(found, createdCRS, true).isIdentity()) { saveKeywords(found); return found; } } return saveCustomizedCRS(text, true, createdCRS); } } return createdCRS; } catch (Exception e) { ExceptionMonitor.show(wktText.getShell(), e); } } if (selectedCRS == null) { String crsCode = (String) ((IStructuredSelection) codesList.getSelection()).getFirstElement(); if (sourceCRS != null && crsCode.equals(sourceCRS.getName().toString())) { System.out.println("source crs: " + sourceCRS.getName().toString()); return sourceCRS; } return createCRS(searchText.getText()); } return selectedCRS; } /** * * @param found * @throws CoreException * @throws IOException * @throws BackingStoreException */ private void saveKeywords( CoordinateReferenceSystem found ) throws CoreException, IOException, BackingStoreException { String[] keywords = keywordsText.getText().split(","); //$NON-NLS-1$ if (keywords.length > 0) { boolean legalKeyword = false; // determine whether there are any keywords that are not blank. for( int i = 0; i < keywords.length; i++ ) { String string = keywords[i]; string = string.trim().toUpperCase(); if (string.length() > 0) { legalKeyword = true; break; } } if (legalKeyword) { saveCustomizedCRS(found.toWKT(), false, found); } } keywordsText.setText(""); //$NON-NLS-1$ wktText.setText(found.toWKT()); } /** * @param text * @param createdCRS * @throws CoreException * @throws IOException * @throws BackingStoreException */ private CoordinateReferenceSystem saveCustomizedCRS( String text, boolean processWKT, CoordinateReferenceSystem createdCRS ) throws CoreException, IOException, BackingStoreException { Preferences root = Preferences.userRoot(); Preferences node = root.node(CUSTOM_ID); int lastID; String code; String name; String newWKT; if (processWKT) { lastID = Integer.parseInt(node.get(LAST_ID, "0")); //$NON-NLS-1$ code = "UDIG:" + lastID; //$NON-NLS-1$ name = createdCRS.getName().toString() + "(" + code + ")";//$NON-NLS-1$ //$NON-NLS-2$ lastID++; node.putInt(LAST_ID, lastID); newWKT = processingWKT(text, lastID); } else { Set<ReferenceIdentifier> ids = createdCRS.getIdentifiers(); if (!ids.isEmpty()) { Identifier id = ids.iterator().next(); code = id.toString(); name = createdCRS.getName().getCode() + " (" + code + ")"; //$NON-NLS-1$ //$NON-NLS-2$ } else { name = code = createdCRS.getName().getCode(); } newWKT = text; } Preferences child = node.node(code); child.put(NAME_ID, name); child.put(WKT_ID, newWKT); String[] keywords = keywordsText.getText().split(","); //$NON-NLS-1$ if (keywords.length > 0) { Preferences keyworkNode = child.node(ALIASES_ID); for( String string : keywords ) { string = string.trim().toUpperCase(); keyworkNode.put(string, string); } } node.flush(); return createdCRS; } /** * Remove the last AUTHORITY if it exists and add a UDIG Authority */ private String processingWKT( String text, int lastID ) { String newWKT; String[] prep = text.split(","); //$NON-NLS-1$ if (prep[prep.length - 2].toUpperCase().contains("AUTHORITY")) { //$NON-NLS-1$ String substring = text.substring(0, text.lastIndexOf(',')); newWKT = substring.substring(0, substring.lastIndexOf(',')) + ", AUTHORITY[\"UDIG\",\"" + (lastID - 1) + "\"]]"; //$NON-NLS-1$ //$NON-NLS-2$ } else { newWKT = text.substring(0, text.lastIndexOf(']')) + ", AUTHORITY[\"UDIG\",\"" + (lastID - 1) + "\"]]"; //$NON-NLS-1$ //$NON-NLS-2$ } wktText.setText(newWKT); return newWKT; } public void setController( Controller controller ) { parentPage = controller; } }