/** * Copyright 2011 Kevin J. Jones (http://www.kevinjjones.co.uk) * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package uk.co.kevinjjones; import java.awt.BorderLayout; import java.awt.Container; import java.awt.FlowLayout; import java.awt.Frame; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.text.ParseException; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; import javax.swing.*; import uk.co.kevinjjones.model.*; import uk.co.kevinjjones.vehicle.SpeedStream; /** * Manager for all run data */ public class RunManager implements ParamHandler { /** * A stream wrapper for RunManager records */ public class RunStream implements ROStream { private ROStream _stream; private int _start; private int _size; private boolean _rebase; private double _base; private ArrayList<Pair<String, String>> _meta = new ArrayList(); public RunStream(ROStream stream, int start, int size) { _stream = stream; _start = start; _size = size; _rebase = stream.getMeta("rebase").equals("true"); } @Override public String name() { return _stream.name(); } @Override public String description() { return _stream.description(); } @Override public String axis() { return _stream.axis(); } @Override public String units() { return _stream.units(); } @Override public int size() { return _size; } @Override public String getString(int position) { assert (position < _size); return _stream.getString(_start + position); } @Override public long getTick(int position) throws ParseException { assert (position < _size); return _stream.getTick(_start + position); } @Override public double getNumeric(int position) throws NumberFormatException { assert (position < _size); if (_rebase) { _base = _stream.getNumeric(_start + 0); _rebase = false; } return _stream.getNumeric(_start + position) - _base; } @Override public String[] getStringSet() { return _stream.getStringSet(); } @Override public Double[] toArray() throws NumberFormatException { return _stream.toArray(); } @Override public void setMeta(String id, String value) { _meta.add(new Pair(id, value)); } @Override public String getMeta(String id) { int i = 0; while (i < _meta.size()) { if (_meta.get(i).first().equals(id)) { return _meta.get(i).second(); } } return ""; } } /** * A single Run wrapper */ public class Run { private String _prefix; private Log _log; private int _start; private int _end; private boolean _isSplit; public Run(String prefix, Log log, int start, int end, boolean isSplit) { _prefix = prefix; _log = log; _start = start; _end = end; _isSplit = isSplit; } public Log log() { return _log; } public String name() { if (_prefix.isEmpty()) { return _log.name(); } else { return _prefix + " " + _log.name(); } } public int length() { return _end - _start; } public boolean isSplit() { return _isSplit; } public int streamCount() { return _log.streamCount(); } public boolean hasStream(String name) { return _log.hasStream(name); } public ROStream getStream(String name) { if (!_log.hasStream(name)) { return null; } return new RunStream(_log.getStream(name), _start, _end - _start); } public ROStream getStream(int index) { if (index < 0 || index >= _log.streamCount()) { return null; } return new RunStream(_log.getStream(index), _start, _end - _start); } /* * Saving for later... */ /* * public double degrees(int i) { * * if (_log.hasLeftSpeed() && _log.hasRightSpeed()) { * * double ls = _log.leftSpeed(_start + i); double rs = * _log.rightSpeed(_start + i); * * Car c = Car.getInstance(); boolean rightTurn = (ls > rs); if * (rightTurn) { return c.getDegrees(ls, rs); } else { return * -c.getDegrees(rs, ls); } } return 0; } * * public double latAccel(int i) { * * if (_log.hasLeftSpeed() && _log.hasRightSpeed()) { * * double ls = _log.leftSpeed(_start + i); double rs = * _log.rightSpeed(_start + i); * * Car c = Car.getInstance(); boolean rightTurn = (ls > rs); if * (rightTurn) { return c.getLatAccel(ls, rs); } else { return * -c.getLatAccel(rs, ls); } } return 0; } * * public double lambda(int index) { if (_lambdaData == null) { * * // Populate with shift _lambdaData = new double[length()]; for (int * j = 0; j < _lambdaData.length; j++) { int idx = (j + lambdaDelay()) % * (_end - _start); _lambdaData[j] = _log.lambda(_start + idx); } * * // Exclude 'bad' section where MAP<80 || map is falling // by 3% * over 0.5 seconds. for (int i = 0; i < _lambdaData.length; i++) { * * // Scan for a switch to bad int r = i; int startBad = 0; while (r + * 1 < _lambdaData.length) { double m = map(r); if (m < 80) { startBad = * r; break; } double d = (map(r + 1) - m) / m; if (d < -0.01) { * startBad = r; break; } r++; } * * if (startBad != 0) { double m = map(startBad); if (m < 80) { while (m * < 80 && startBad<_lambdaData.length) { _lambdaData[startBad] = 0; * startBad++; m = map(startBad); } i=startBad; } else { // Test for * sufficient fall double low = 0.97 * map(startBad); for (int k = * startBad; k < startBad + 5 && k<_lambdaData.length; k++) { if (map(k) * < low) { // Walk until map increases again while (map(k + 1) < * map(k)) { k++; } * * // Got one, so zero out for (int p = startBad - 1; p <= k+1; p++) { * _lambdaData[p] = 0; } i = k; break; } } } } } } * * return _lambdaData[index]; } * */ } private static RunManager _instance = null; private static Frame _frame = null; private List<Log> _logs = new LinkedList<Log>(); private List<Run> _runs = new LinkedList<Run>(); private boolean _autoSplit = false; private int _KPH = 0; protected RunManager() { // Load preferences Preferences prefs = Preferences.userNodeForPackage(RunManager.class); _KPH = prefs.getInt("KPH2", 0); _autoSplit = prefs.getBoolean("AutoSplit", false); } // Singleton access public static RunManager getInstance() { if (_instance == null) { _instance = new RunManager(); } return _instance; } // Set UI frame for allow dialog boxes to be setup public void setFrame(Frame frame) { _frame = frame; } @Override public String getParameter(String name, boolean prompt) { if (name.equals("isKPH")) { if (_KPH == 0 && prompt) { JDialog dialog = new JDialog(_frame, "DTA System Units", true); Container content = dialog.getContentPane(); OptionsDialog oDlg = new OptionsDialog(); content.add(oDlg); dialog.pack(); dialog.setLocationRelativeTo(_frame); dialog.setVisible(true); } if (_KPH == 1) { return "true"; } if (_KPH == 2) { return "false"; } } return null; } public void addLogfile(File file, WithError<Boolean, BasicError> ok) throws IOException { // Parse logile and load runs from it Log l = new Log(file, this, ok); if (ok.value()) { _logs.add(l); addSessions(l, ok); } } public Log[] getLogs() { return _logs.toArray(new Log[0]); } public Run[] getRuns() { return _runs.toArray(new Run[0]); } private void addSessions(Log l, WithError<Boolean, BasicError> ok) { // Now look for multiple sessions ROStream sess = l.getStream(Log.SESSION_STREAM); String c = sess.getString(0); int begin = 1; int end = 1; int s = 1; int runs = 0; for (; end < sess.size(); end++) { if (!sess.getString(end).equals(c)) { runs += addRuns(l, s++, begin, end - 1 - begin); begin = end; c = sess.getString(begin); } } runs += addRuns(l, s++, begin, end - 1 - begin); if (isAutoSplit() && runs == 0) { ok.addError(new BasicError(BasicError.WARN, "No runs detected, try " + "loading file with auto spliting turned off")); } else if (isAutoSplit()) { ok.addError(new BasicError(BasicError.INFO, "Loaded " + (s - 1) + " sessions from " + l.name() + ", containing " + runs + " runs.")); } else { ok.addError(new BasicError(BasicError.INFO, "Loaded " + (s - 1) + " sessions from " + l.name() + ".")); } } private int addRuns(Log l, int session, int begin, int size) { // Should we also split? int endSession = begin + size; int id = 1; ROStream speed = l.getStream(SpeedStream.NAME); if (speed != null && isAutoSplit()) { // Look for runs int at = begin; while (true) { int start = findStartPoint(speed, at, endSession); if (start == -1) { break; } int end = findEndPoint(speed, start, endSession); if (end == -1) { break; } _runs.add(new Run("S" + session + "#" + id++, l, start, end, true)); at = end; } return id - 1; } else { _runs.add(new Run("S" + session, l, begin, begin + size, false)); return 1; } } /** * Test if the current row is a crossing point to the next speed unit. We * use two datums here, crossing from 0->1 kph and crossing over a * speedBucket * * @param data * @param start * @param index * @return */ private static int findStartPoint(ROStream speed, int start, int endSession) { // Loop speed until >60kph int r = start; for (; r < endSession; r++) { if (speed.getNumeric(r) > 60) { break; } } // Didn't find if (r == endSession) { return -1; } // Now loop back to locate the start at lowest speed <10kph while (r > start) { double s = speed.getNumeric(r); double low = s; if (s < 10) { // In right area, find first lowest int t = r - 1; while (t > 0) { double s2 = speed.getNumeric(t); if (s2 < low) { low = s2; r = t; } else { return r; } t--; } } r--; } // Not found; return -1; } private static int findEndPoint(ROStream speed, int start, int endSession) { // Loop speed until >60kph int r = start; for (; r < endSession; r++) { if (speed.getNumeric(r) > 60) { break; } } // Didn't find if (r == endSession) { return -1; } // Continue loop until < 5km/h for (; r < endSession; r++) { if (speed.getNumeric(r) < 5) { return r; } } return -1; } private void flushPrefs() { Preferences prefs = Preferences.userNodeForPackage(RunManager.class); prefs.putInt("KPH2", _KPH); prefs.putBoolean("AutoSplit", _autoSplit); try { prefs.flush(); } catch (BackingStoreException ex) { final JDialog dialog = new JDialog(_frame, "Error", true); JPanel displayArea = new JPanel(); displayArea.setLayout(new BoxLayout(displayArea, BoxLayout.PAGE_AXIS)); JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEADING)); displayArea.add(panel); panel.add(new JLabel("Message:")); panel.add(new JLabel("Failed to save user preferences")); JPanel epanel = new JPanel(new FlowLayout(FlowLayout.LEADING)); displayArea.add(epanel); epanel.add(new JLabel("Exception:")); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw, true); ex.printStackTrace(pw); pw.flush(); sw.flush(); epanel.add(new JLabel("<html>" + sw.toString().replaceAll("\n", "<br>"))); JPanel buttonArea = new JPanel(); FlowLayout buttonLayout = new FlowLayout(FlowLayout.RIGHT); buttonArea.setLayout(buttonLayout); JButton okBtn = new JButton("OK"); buttonArea.add(okBtn); okBtn.addActionListener( new ActionListener() { @Override public void actionPerformed(ActionEvent e) { dialog.setVisible(false); } }); JPanel dialogPanel = new JPanel(); BorderLayout dialogLayout = new BorderLayout(); dialogPanel.setLayout(dialogLayout); dialogPanel.add(displayArea, BorderLayout.CENTER); dialogPanel.add(buttonArea, BorderLayout.PAGE_END); Container content = dialog.getContentPane(); content.add(dialogPanel); dialog.pack(); dialog.setLocationRelativeTo(_frame); dialog.setVisible(true); } } public boolean isAutoSplit() { return _autoSplit; } public void setAutoSplit(boolean isAutoSplit) { _autoSplit = isAutoSplit; flushPrefs(); } public int getKPH() { return _KPH; } public void setKPH(boolean isKPH) { _KPH = isKPH ? 1 : 2; flushPrefs(); } public void unsetKPH() { _KPH = 0; flushPrefs(); } }