package com.opendoorlogistics.components.matrixexporter; import java.awt.BorderLayout; import java.awt.Dimension; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.Serializable; import java.util.HashMap; import java.util.Map; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.Icon; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextPane; import javax.xml.bind.annotation.XmlRootElement; import org.hsqldb.lib.HashSet; import com.opendoorlogistics.api.ODLApi; import com.opendoorlogistics.api.components.ComponentConfigurationEditorAPI; import com.opendoorlogistics.api.components.ComponentControlLauncherApi; import com.opendoorlogistics.api.components.ComponentExecutionApi; import com.opendoorlogistics.api.components.ODLComponent; import com.opendoorlogistics.api.components.PredefinedTags; import com.opendoorlogistics.api.components.ComponentControlLauncherApi.ControlLauncherCallback; import com.opendoorlogistics.api.distances.DistancesConfiguration; import com.opendoorlogistics.api.distances.ODLCostMatrix; import com.opendoorlogistics.api.distances.DistancesOutputConfiguration.OutputDistanceUnit; import com.opendoorlogistics.api.distances.DistancesOutputConfiguration.OutputTimeUnit; import com.opendoorlogistics.api.geometry.LatLong; import com.opendoorlogistics.api.scripts.ScriptTemplatesBuilder; import com.opendoorlogistics.api.standardcomponents.MatrixExporter; import com.opendoorlogistics.api.tables.ODLColumnType; import com.opendoorlogistics.api.tables.ODLDatastore; import com.opendoorlogistics.api.tables.ODLDatastoreAlterable; import com.opendoorlogistics.api.tables.ODLTable; import com.opendoorlogistics.api.tables.ODLTableAlterable; import com.opendoorlogistics.api.tables.ODLTableDefinition; import com.opendoorlogistics.api.tables.ODLTableDefinitionAlterable; import com.opendoorlogistics.api.tables.ODLTableReadOnly; import com.opendoorlogistics.api.tables.ODLTime; import com.opendoorlogistics.api.ui.Disposable; import com.opendoorlogistics.api.ui.UIFactory.FilenameChangeListener; import com.opendoorlogistics.core.gis.map.data.LatLongImpl; import com.opendoorlogistics.core.utils.Exceptions; import com.opendoorlogistics.core.utils.Serialization; import com.opendoorlogistics.core.utils.UpdateTimer; import com.opendoorlogistics.core.utils.strings.Strings; import com.opendoorlogistics.core.utils.ui.FileBrowserPanel; import com.opendoorlogistics.utils.ui.Icons; public class MatrixExporterComponent implements ODLComponent, MatrixExporter { @Override public String getId() { return "com.opendoorlogistics.components.matrixexporter"; } @Override public String getName() { return "Travel matrix text file exporter"; } @Override public ODLDatastore<? extends ODLTableDefinition> getIODsDefinition(ODLApi api, Serializable configuration) { ODLDatastoreAlterable<? extends ODLTableDefinitionAlterable> ds = api.tables().createDefinitionDs(); ODLTableDefinitionAlterable table = ds.createTable("Locations", -1); if (configuration != null && (((TravelMatrixExporterConfig) configuration)).isIncludeId()) { table.addColumn(-1, PredefinedTags.ID, ODLColumnType.STRING, 0); } table.addColumn(-1, PredefinedTags.LATITUDE, ODLColumnType.DOUBLE, 0); table.addColumn(-1, PredefinedTags.LONGITUDE, ODLColumnType.DOUBLE, 0); return ds; } @Override public ODLDatastore<? extends ODLTableDefinition> getOutputDsDefinition(ODLApi api, int mode, Serializable configuration) { return null; } @Override public void execute(ComponentExecutionApi api, int mode, Object configuration, ODLDatastore<? extends ODLTable> ioDs, ODLDatastoreAlterable<? extends ODLTableAlterable> outputDs) { TravelMatrixExporterConfig c = (TravelMatrixExporterConfig) configuration; if(Strings.isEmptyWhenStandardised(c.getExportFilename())){ throw new RuntimeException("The output filename for the matrix is empty. You need to set the output filename in the matrix exporter settings."); } c = (TravelMatrixExporterConfig)Serialization.deepCopy(c); c.getDistancesConfiguration().getOutputConfig().setOutputDistanceUnit(OutputDistanceUnit.KILOMETRES); c.getDistancesConfiguration().getOutputConfig().setOutputTimeUnit(OutputTimeUnit.MILLISECONDS); ODLTableReadOnly table = ioDs.getTableAt(0); int nrows = table.getRowCount(); // create table in the format expected by distances ODLApi odlApi = api.getApi(); ODLTableAlterable copyTable = odlApi.tables().createAlterableTable("Locations"); copyTable.addColumn(-1, PredefinedTags.LOCATION_KEY, ODLColumnType.STRING, 0); copyTable.addColumn(-1, PredefinedTags.LATITUDE, ODLColumnType.DOUBLE, 0); copyTable.addColumn(-1, PredefinedTags.LONGITUDE, ODLColumnType.DOUBLE, 0); Map<String,LatLong> idToLL = api.getApi().stringConventions().createStandardisedMap(); Map<String,String> standardToOriginalId = api.getApi().stringConventions().createStandardisedMap(); for(int oldRow =0 ; oldRow<nrows ; oldRow++){ int newRow=copyTable.createEmptyRow(-1); if(!c.isIncludeId()){ // create dummy id copyTable.setValueAt(Integer.toString(oldRow), newRow, 0); // copy lat and long copyTable.setValueAt(table.getValueAt(oldRow, 0), newRow, 1); copyTable.setValueAt(table.getValueAt(oldRow, 1), newRow, 2); }else{ if(Strings.isEmptyWhenStandardised(table.getValueAt(oldRow, 0))){ throw new RuntimeException("Found input location with empty id on row " + (oldRow+1)); } // copy all columns for(int j=0;j<3;j++){ copyTable.setValueAt(table.getValueAt(oldRow, j), newRow, j); } } String id =(String)copyTable.getValueAt(newRow, 0); idToLL.put(id, new LatLongImpl((Double)copyTable.getValueAt(newRow, 1), (Double)copyTable.getValueAt(newRow, 2))); standardToOriginalId.put(id, id); } ODLCostMatrix matrix = api.calculateDistances(c.getDistancesConfiguration(), copyTable); // now write to file UpdateTimer timer = new UpdateTimer(250); long nbLines=0; File file = new File(c.getExportFilename()); try(BufferedWriter writer = new BufferedWriter(new FileWriter(file))){ // header... if(c.isIncludeId()){ writer.write("FromID\t"); } writer.write("FromLatitude\t"); writer.write("FromLongitude\t"); if(c.isIncludeId()){ writer.write("ToID\t"); } writer.write("ToLatitude\t"); writer.write("ToLongitude\t"); writer.write(Strings.convertEnumToDisplayFriendly(c.getDistancesConfiguration().getOutputConfig().getOutputDistanceUnit()) + "\t"); writer.write("Time"); writer.write(System.lineSeparator()); int nf= matrix.getNbFroms(); int nt = matrix.getNbTos(); for(int f = 0 ; f<nf ; f++){ for(int t = 0 ; t<nt; t++){ // From if(c.isIncludeId()){ writer.write(standardToOriginalId.get(matrix.getFromId(f))+"\t"); } LatLong from = idToLL.get(matrix.getFromId(f)); writer.write(""+ from.getLatitude() + "\t"); writer.write("" + from.getLongitude() +"\t"); // To if(c.isIncludeId()){ writer.write(standardToOriginalId.get(matrix.getToId(t)) +"\t"); } LatLong to = idToLL.get(matrix.getToId(t)); writer.write("" + to.getLatitude()+ "\t"); writer.write("" + to.getLongitude() + "\t"); // Distance writer.write("" + matrix.get(f, t, ODLCostMatrix.COST_MATRIX_INDEX_DISTANCE)); writer.write("\t"); // Time double millis = matrix.get(f, t, ODLCostMatrix.COST_MATRIX_INDEX_TIME); ODLTime time = new ODLTime(Math.round(millis)); writer.write(time.toString()); // New line writer.write(System.lineSeparator()); nbLines++; if(timer.isUpdate()){ StringBuilder builder = new StringBuilder(); builder.append("Written " + nbLines + " out of "+ (nf*nt) + " lines to file."); } } if(api.isCancelled()){ throw new RuntimeException("User cancelled"); } } } catch (Exception e) { throw Exceptions.asUnchecked(e); } api.submitControlLauncher(new ControlLauncherCallback() { @Override public void launchControls(ComponentControlLauncherApi launcherApi) { // build up text StringBuilder builder = new StringBuilder(); builder.append("Finished exporting " + matrix.getNbFroms() + "x" + matrix.getNbTos() + " matrix to file " + file.getAbsolutePath()); // create panel to report it JTextPane textPane = new JTextPane(); textPane.setText(builder.toString()); JScrollPane scroller = new JScrollPane(textPane); class MyPanel extends JPanel implements Disposable{ @Override public void dispose() { // TODO Auto-generated method stub } } MyPanel panel = new MyPanel(); panel.setLayout(new BorderLayout()); panel.add(scroller,BorderLayout.CENTER); panel.setPreferredSize(new Dimension(400, 200)); launcherApi.registerPanel("Export finished",null, panel, false); } }); // api. } @Override public Class<? extends Serializable> getConfigClass() { return TravelMatrixExporterConfig.class; } @Override public JPanel createConfigEditorPanel(ComponentConfigurationEditorAPI api, int mode, Serializable config, boolean isFixedIO) { JPanel panel = new JPanel(); panel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); // JLabel label = new JLabel("Travel matrix exporter component. Select the output filename when you run the component."); panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); // panel.add(label); TravelMatrixExporterConfig c = (TravelMatrixExporterConfig) config; FileBrowserPanel fileBrowserPanel = new FileBrowserPanel("Output file", c.getExportFilename(), new FilenameChangeListener(){ @Override public void filenameChanged(String newFilename) { c.setExportFilename(newFilename); } }, false, "OK" ); panel.add(Box.createRigidArea(new Dimension(1, 10))); panel.add(fileBrowserPanel); if (api != null) { // add(Box.createRigidArea(new Dimension(0,6))); // With flags=0 we (deliberately) loose the ability to modify cost type or output travel units (which are ignored) long flags=0; JPanel distances = api.getApi().uiFactory().createDistancesEditor(c.getDistancesConfiguration(), flags); panel.add(Box.createRigidArea(new Dimension(1, 10))); JPanel leftAlignHack = new JPanel(new BorderLayout()); leftAlignHack.add(distances, BorderLayout.WEST); panel.add(leftAlignHack); // distances.setBorder(BorderFactory.createTitledBorder("Distances")); // leftAlignHack.add(distances, BorderLayout.WEST); // add(leftAlignHack); } return panel; } @Override public long getFlags(ODLApi api, int mode) { return ODLComponent.FLAG_ALLOW_USER_INTERACTION_WHEN_RUNNING; } @Override public Icon getIcon(ODLApi api, int mode) { return Icons.loadFromStandardPath("export-matrix.png"); } @Override public boolean isModeSupported(ODLApi api, int mode) { return mode == ODLComponent.MODE_DEFAULT; } @Override public void registerScriptTemplates(ScriptTemplatesBuilder templatesApi) { for (int includeId = 0; includeId <= 1; includeId++) { String name = "Export travel matrix,"; if (includeId == 0) { name += " without id field"; }else{ name += " including id field"; } Serializable config = createConfig(includeId==1); templatesApi.registerTemplate(name, name, name, getIODsDefinition(templatesApi.getApi(), config), config); } } @Override public Serializable createConfig(boolean exportIds) { TravelMatrixExporterConfig config = new TravelMatrixExporterConfig(); config.setIncludeId(exportIds); return config; } @XmlRootElement public static class TravelMatrixExporterConfig implements Serializable { private boolean includeId; private String exportFilename = ""; private DistancesConfiguration distancesConfiguration = new DistancesConfiguration(); // private String exportFilename; public boolean isIncludeId() { return includeId; } public void setIncludeId(boolean includeId) { this.includeId = includeId; } public DistancesConfiguration getDistancesConfiguration() { return distancesConfiguration; } public void setDistancesConfiguration(DistancesConfiguration distancesConfiguration) { this.distancesConfiguration = distancesConfiguration; } public String getExportFilename() { return exportFilename; } public void setExportFilename(String exportFilename) { this.exportFilename = exportFilename; } } }