/*
* uDig - User Friendly Desktop Internet GIS client
* http://udig.refractions.net
* (C) 2004, Refractions Research Inc.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* (http://www.eclipse.org/legal/epl-v10.html), and the Refractions BSD
* License v1.0 (http://udig.refractions.net/files/bsd3-v10.html).
*
*/
package org.locationtech.udig.ui;
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.regex.Matcher;
import java.util.regex.Pattern;
import org.locationtech.udig.internal.ui.UiPlugin;
import org.locationtech.udig.ui.internal.Messages;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.preferences.InstanceScope;
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.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;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;
/**
* Creates a Control for choosing a Coordinate Reference System.
*
* @author jeichar
* @since 0.6.0
*/
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.CRSChooser_keywordsLabel);
keywordsLabel.setLayoutData(gridData);
keywordsLabel.setToolTipText(Messages.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.CRSChooser_tooltip);
gridData = new GridData(SWT.FILL, SWT.NONE, true, false);
gridData.horizontalSpan = 2;
Label editorLabel = new Label(composite, SWT.NONE);
editorLabel.setText(Messages.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.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) {
UiPlugin.log("", e); //$NON-NLS-1$
}
}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){
UiPlugin.log(crs.toString()+" cannot be formatted as WKT", e); //$NON-NLS-1$
wktText.setText(Messages.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.CRSChooser_tab_standardCRS);
Control stdCRS = createStandardCRSControl(folder);
standard.setControl(stdCRS);
TabItem custom = new TabItem(folder, SWT.NONE);
custom.setText(Messages.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.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) {
UiPlugin.trace( CRSChooser.class, "CRS Authority:"+e.getMessage(), e );
}
}
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 = UiPlugin.getUserPreferences();
Preferences node = root.node(InstanceScope.SCOPE).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) {
UiPlugin.log("", e); //$NON-NLS-1$
}
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) {
UiPlugin.log("Error creating CRS object, trying more...", e);
}
}
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) {
UiPlugin.log(wkt, e);
child.removeNode();
}
}
}
} catch (Exception e) {
UiPlugin.log(null, e);
}
return null; // should throw an exception?
}
private Preferences findNode( String code ) {
try {
Preferences root = UiPlugin.getUserPreferences();
Preferences node = root.node(InstanceScope.SCOPE).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) {
UiPlugin.log("Error loading", e);//$NON-NLS-1$
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) {
UiPlugin.log("", e); //$NON-NLS-1$
}
}
if (selectedCRS == null) {
String crsCode = (String) ((IStructuredSelection) codesList.getSelection()).getFirstElement();
if(sourceCRS != null && crsCode!=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 = UiPlugin.getUserPreferences();
Preferences node = root.node(InstanceScope.SCOPE).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;
}
}