package org.activityinfo.geoadmin;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.vividsolutions.jts.geom.Envelope;
import net.miginfocom.swing.MigLayout;
import org.activityinfo.geoadmin.model.*;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import java.util.zip.GZIPOutputStream;
/**
* User interface for matching imported features with their parents in the
* existing hierarchy.
*
*/
public class ImportWindow extends JDialog {
private static final Logger LOGGER = Logger.getLogger(ImportWindow.class.getName());
private ActivityInfoClient client;
private List<AdminEntity> parentEntities;
private ImportTableModel tableModel;
private ImportForm importForm;
private ImportSource source;
private ParentGuesser scorer;
private JLabel scoreLabel;
private Country country;
private AdminLevel parentLevel;
private JTable table;
public ImportWindow(JFrame parent, ActivityInfoClient client,
Country country,
AdminLevel parentLevel,
File shapeFile) throws Exception {
super(parent, "Import - " + shapeFile.getName(), Dialog.ModalityType.APPLICATION_MODAL);
setSize(650, 350);
setLocationRelativeTo(parent);
this.client = client;
this.country = country;
this.parentLevel = parentLevel;
source = new ImportSource(shapeFile);
if (parentLevel == null) {
parentEntities = Lists.newArrayList();
} else {
parentEntities = sort(client.getAdminEntities(parentLevel));
}
scorer = new ParentGuesser(source, parentEntities);
importForm = new ImportForm(source, parentEntities);
tableModel = new ImportTableModel(source);
JComboBox parentComboBox = new JComboBox(parentEntities.toArray());
parentComboBox.setEditable(false);
JComboBox actionComboBox = new JComboBox(ImportAction.values());
actionComboBox.setEditable(false);
table = new JTable(tableModel);
table.getColumnModel().getColumn(ImportTableModel.PARENT_COLUMN).setCellEditor(
new DefaultCellEditor(parentComboBox));
table.getColumnModel().getColumn(ImportTableModel.ACTION_COLUMN).setCellEditor(
new DefaultCellEditor(actionComboBox));
table.setDefaultRenderer(Object.class,
new ImportTableCellRenderer(tableModel, scorer));
table.setAutoCreateRowSorter(true);
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
onSelectionChanged(e);
}
});
scoreLabel = new JLabel();
JLabel countLabel = new JLabel(source.getFeatureCount() + " features");
JPanel panel = new JPanel(new MigLayout("fill"));
panel.add(importForm, "wrap");
panel.add(new JScrollPane(table,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED), "span, wrap,grow");
panel.add(scoreLabel, "height 25!, growx");
panel.add(countLabel);
getContentPane().add(createToolBar(), BorderLayout.PAGE_START);
getContentPane().add(panel, BorderLayout.CENTER);
}
private void onSelectionChanged(ListSelectionEvent e) {
int row = e.getFirstIndex();
int featureIndex = table.convertRowIndexToModel(row);
showScore(featureIndex);
}
/**
* Display the parent match score of the selected item in the status bar
*
* @param featureIndex
*/
private void showScore(int featureIndex) {
AdminEntity parent = tableModel.getParent(featureIndex);
if (parent == null) {
scoreLabel.setText("");
} else {
ImportFeature feature = tableModel.getFeatureAt(featureIndex);
scoreLabel.setText(String.format("Scores: Geo: %.2f Name: %.2f Code: %.2f",
scorer.scoreGeography(feature, parent),
scorer.scoreName(feature, parent),
scorer.scoreCodeMatch(feature, parent)));
}
}
private JToolBar createToolBar() {
JButton guessButton = new JButton("Guess Parents");
guessButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
guessParents();
}
});
JButton linkCodeButton = new JButton("Link Parents via Code");
linkCodeButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
guessParentsWithCode();
}
});
JButton updateButton = new JButton("Import");
updateButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
doImport();
} catch (FileNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
});
JButton saveButton = new JButton("Write import");
saveButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
doWriteImport();
} catch(Exception ex) {
ex.printStackTrace();
}
}
});
JToolBar toolBar = new JToolBar();
toolBar.setFloatable(false);
toolBar.add(guessButton);
toolBar.add(linkCodeButton);
toolBar.add(updateButton);
toolBar.add(saveButton);
toolBar.addSeparator();
return toolBar;
}
protected void doImport() throws FileNotFoundException {
AdminLevel newLevel = buildUpdate();
if(parentLevel != null) {
client.postChildLevel(parentLevel, newLevel);
} else {
client.postRootLevel(country, newLevel);
}
// hide window
setVisible(false);
}
private void doWriteImport() throws IOException {
File tempFile = File.createTempFile("level", ".sql.gz");
try(
PrintWriter out = new PrintWriter(
new GZIPOutputStream(
new FileOutputStream(tempFile))) ) {
AdminLevel newLevel = buildUpdate();
out.println("BEGIN;");
out.println(String.format("INSERT INTO adminlevel (name, countryid, parentid) VALUES (%s, %d, %d);",
Sql.quote(newLevel.getName()),
country.getId(),
parentLevel.getId()));
out.println("select @newLevelId:=last_insert_id();");
out.println(String.format("INSERT INTO locationtype (name, countryid, boundadminlevelid, reuse) " +
"VALUES (%s, %d, @newLevelId, 0);",
Sql.quote(newLevel.getName()),
country.getId()));
out.println("COMMIT;");
out.println("BEGIN;");
int count = 0;
boolean first = true;
for(AdminEntity entity : newLevel.getEntities()) {
if(first) {
out.println("INSERT DELAYED INTO adminentity " +
"(adminlevelid, name, code, adminentityparentid, x1, y1, x2, y2) VALUES");
first = false;
} else {
out.println(",");
}
out.print(String.format("(@newLevelId, %s, %s, %s, %f, %f, %f, %f)",
Sql.quote(entity.getName()),
Sql.quote(entity.getCode()),
parentLevel == null ? "null" : Integer.toString(entity.getParentId()),
entity.getBounds().getX1(),
entity.getBounds().getY1(),
entity.getBounds().getX2(),
entity.getBounds().getY2()));
count ++;
if(count % 1000 == 0) {
out.println(";");
first = true;
}
}
out.println(";");
out.println("COMMIT;");
}
System.out.println("Wrote to " + tempFile.getAbsolutePath());
}
private AdminLevel buildUpdate() {
int nameAttribute = importForm.getNameAttributeIndex();
int codeAttribute = importForm.getCodeAttributeIndex();
List<AdminEntity> entities = Lists.newArrayList();
Map<ImportKey, AdminEntity> entityMap = Maps.newHashMap();
for (int i = 0; i != tableModel.getRowCount(); ++i) {
if(tableModel.getActionAt(i) == ImportAction.IMPORT) {
ImportFeature feature = tableModel.getFeatureAt(i);
String featureName = feature.getAttributeStringValue(nameAttribute);
AdminEntity parent = tableModel.getParent(i);
if(!validateFeature(feature, featureName, parent)) {
continue;
}
if(Strings.isNullOrEmpty(featureName)) {
throw new RuntimeException("Feature " + i + " has an empty name");
}
// we can't have two entities with the same name within a
// given parent. This happens often because secondary exterior rings
// are stored as separate features.
ImportKey key = new ImportKey(parent, featureName);
if(!entityMap.containsKey(key)) {
AdminEntity entity = new AdminEntity();
entity.setName(featureName);
if (codeAttribute != -1) {
entity.setCode(feature.getAttributeStringValue(codeAttribute));
}
Bounds bounds = GeoUtils.toBounds(feature.getEnvelope());
entity.setBounds(bounds);
if (importForm.isGeometryImported()) {
entity.setGeometry(feature.getGeometry());
}
if (parentLevel != null) {
entity.setParentId(parent.getId());
}
entities.add(entity);
entityMap.put(key, entity);
} else {
// add this geometry to the existing entity
LOGGER.info("Merging geometry for entity named '" + featureName + "'");
AdminEntity entity = entityMap.get(key);
Envelope bounds = GeoUtils.toEnvelope(entity.getBounds());
bounds.expandToInclude(feature.getEnvelope());
entity.setBounds(GeoUtils.toBounds(bounds));
if(importForm.isGeometryImported()) {
entity.setGeometry( entity.getGeometry().union(feature.getGeometry()) );
}
}
}
}
AdminLevel newLevel = new AdminLevel();
newLevel.setName(importForm.getLevelName());
if (parentLevel != null) {
newLevel.setParentId(parentLevel.getId());
}
newLevel.setEntities(entities);
return newLevel;
}
private boolean validateFeature(ImportFeature feature, String featureName, AdminEntity parent) {
if(parentLevel != null) {
if(parent == null) {
System.err.println(feature + " has no parent");
return false;
}
}
if(Strings.nullToEmpty(featureName).length() == 0) {
System.err.println(feature + " has no name");
return false;
}
return true;
}
private List<AdminEntity> sort(List<AdminEntity> adminEntities) {
Collections.sort(adminEntities, new Comparator<AdminEntity>() {
@Override
public int compare(AdminEntity a, AdminEntity b) {
return a.getName().compareTo(b.getName());
}
});
return adminEntities;
}
private void guessParents() {
try {
AdminEntity[] parents = scorer.run();
for (int featureIndex = 0; featureIndex != parents.length; ++featureIndex) {
tableModel.setValueAt(parents[featureIndex], featureIndex, ImportTableModel.PARENT_COLUMN);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void guessParentsWithCode() {
int index = importForm.getParentCodeAttributeIndex();
if(index < 0) {
System.out.println("Choose parent code first");
return;
}
Map<String, AdminEntity> codeMap = Maps.newHashMap();
for(AdminEntity entity : this.parentEntities) {
codeMap.put(entity.getCode(), entity);
}
for (int featureIndex = 0; featureIndex != source.getFeatureCount(); ++featureIndex) {
String code = source.getFeatures().get(featureIndex).getAttributeStringValue(index);
AdminEntity entity = codeMap.get(code);
tableModel.setValueAt(entity, featureIndex, ImportTableModel.PARENT_COLUMN);
}
}
}