package com.applang.components;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.StringWriter;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Locale;
import java.util.Map;
import java.util.regex.MatchResult;
import java.util.regex.Pattern;
import javax.swing.Box;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import org.gjt.sp.jedit.View;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import com.applang.PluginUtils;
import com.applang.berichtsheft.BerichtsheftActivity;
import com.applang.berichtsheft.BerichtsheftApp;
import com.applang.berichtsheft.plugin.BerichtsheftPlugin;
import com.applang.berichtsheft.plugin.BerichtsheftShell;
import com.applang.provider.WeatherInfo.Weathers;
import console.Console;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.net.Uri;
import android.util.Log;
import android.widget.TextView;
import static com.applang.Util.*;
import static com.applang.Util2.*;
import static com.applang.SwingUtil.*;
import static com.applang.PluginUtils.*;
public class WeatherManager extends ActionPanel
{
private static final String TAG = WeatherManager.class.getSimpleName();
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
final DataView dataView = new DataView();
String title = "WeatherInfo database";
WeatherManager weatherManager = new WeatherManager(dataView,
null,
title);
ActionPanel.createAndShowGUI(title,
new Dimension(1000, 200),
Behavior.EXIT_ON_CLOSE,
weatherManager,
new Function<Component>() {
public Component apply(Object...params) {
Component c = findFirstComponent(dataView, "south");
if (c != null)
dataView.remove(c);
return dataView.getUIComponent();
}
});
}
});
}
private JLabel uriLabel;
private DataView dataView;
public WeatherManager() {
super(null);
}
public WeatherManager(IComponent dataView, Object... params) {
super(dataView, params);
this.dataView = (DataView) iComponent;
addButton(this, ActionType.CALENDAR.index(), new InfoAction(ActionType.CALENDAR));
addButton(this, 4, new InfoAction("Weather"));
addButton(this, ActionType.DATABASE.index(), new InfoAction(ActionType.DATABASE));
JPopupMenu popupMenu = newPopupMenu(
objects(ActionType.IMPORT.description(), new InfoAction(ActionType.IMPORT)),
objects(ActionType.TEXT.description(), new InfoAction(ActionType.TEXT))
);
attachDropdownMenu(buttons[4], popupMenu);
add(Box.createHorizontalStrut(10));
add(uriLabel = new JLabel());
}
@Override
public void start(Object... params) {
super.start(params);
dbFilePath = getSetting("database", "");
}
@Override
public void finish(Object... params) {
if (getCon() != null)
try {
updateOnRequest();
getCon().close();
} catch (Exception e) {
handleException(e);
}
putSetting("database", dbFilePath);
super.finish(params);
}
public class InfoAction extends CustomAction
{
public InfoAction(ActionType type) {
super(type);
}
public InfoAction(String text) {
super(text);
}
@Override
protected void action_Performed(ActionEvent ae) {
ActionType t = (ActionType)getType();
if (t != null) {
switch (t) {
case DATABASE:
if (dataView.configureData(null, false)) {
dbFilePath = dataView.getDataConfiguration().getPath();
if (notNullOrEmpty(dbFilePath))
openConnection(dbFilePath);
uriLabel.setText(dbFilePath);
}
break;
case CALENDAR:
DatePicker.Period.pick(0);
break;
case IMPORT:
parseAndEvaluate(location, DatePicker.Period.loadParts(0), true, null);
break;
default:
return;
}
}
}
}
public String location = "10519"; // BONN-ROLEBER
private static final String URL_MONTHREP = "http://www.mundomanz.com/meteo_p/monthrep?" +
"countr=GERMANY&" +
"ind=%s&" +
"year=%s&" +
"month=%s&" +
"l=1&" +
"action=display";
/*
D: observation day.
h: UTC observation time.
T: air temperature (ºC).
RH: air relative humidity (%).
P/Gh: sea level pressure (hpa) or geopotential height (m).
WI: wind direction and speed (km/h).
CC: total cloud cover(eighths).
LC: cover and type of low clouds.
MC: cover and type of middle clouds.
HC: type of high clouds.
PR: amount of precipitation and measuring period.
MT: maximun temperature.
mT: minimum temperature.
WE: weather.
*/
private static final String URL_SUMMARY = "http://www.mundomanz.com/meteo_p/byind?" +
"countr=GERMANY&" +
"ind=%s&" +
"year=%s&" +
"month=%s&" +
"day=%s&" +
"n_days=%d&" +
"trans=PA&" +
"time=all&" +
"l=1&action=display";
String urlString(boolean broken, String location, int...dateParts) {
String url;
String monthString = "0" + dateParts[1];
monthString = monthString.substring(monthString.length() - 2, monthString.length());
switch (dateParts[3]) {
case DatePicker.Period.MONTH:
url = String.format(URL_MONTHREP,
location,
"" + dateParts[0],
monthString);
break;
default:
url = String.format(URL_SUMMARY,
location,
"" + dateParts[0],
monthString,
"" + dateParts[2],
dateParts[3]);
break;
}
if (broken)
url = url.replaceAll("(\\?|\\&)", "$1\n");
return url;
}
public static Uri siteUri(String translation, String location, String time, int...dateParts) {
Uri.Builder builder = new Uri.Builder()
.scheme("http")
.authority("www.mundomanz.com")
.appendPath("meteo_p")
.appendQueryParameter("countr", "GERMANY")
.appendQueryParameter("ind", location)
.appendQueryParameter("year", "" + dateParts[0])
.appendQueryParameter("month", String.format("%02d", dateParts[1]));
switch (dateParts[3]) {
case DatePicker.Period.MONTH:
builder = builder.appendPath("monthrep");
break;
default:
builder = builder.appendPath("byind")
.appendQueryParameter("day", "" + dateParts[2])
.appendQueryParameter("n_days", "" + dateParts[3])
.appendQueryParameter("time", time)
.appendQueryParameter("trans", translation);
break;
}
return builder
.appendQueryParameter("l", "1")
.appendQueryParameter("action", "display")
.build();
}
private Document parseSite(String location, int[] dateParts) {
Document doc = null;
switch (dateParts[3]) {
case DatePicker.Period.MONTH:
break;
default:
dateParts = DatePicker.extendPeriod(1, dateParts);
}
Uri uri = siteUri("PA", location, "all", dateParts);
BerichtsheftPlugin.print("connecting '%s'\n... ", uri);
long millis = System.currentTimeMillis();
try {
doc = Jsoup.connect(uri.toString())
.timeout(100000)
.get();
} catch (Exception e) {
handleException(e);
}
BerichtsheftPlugin.print("%d sec(s)", (System.currentTimeMillis() - millis) / 1000, NEWLINE);
return doc;
}
public void parseAndEvaluate(final String location, final int[] period,
final boolean popup,
final Function<Void> followUp, final Object... params)
{
Boolean async = param_Boolean(true, 1, params);
if (!async) {
Document doc = parseSite(location, period);
evaluate(doc, popup);
if (followUp != null)
followUp.apply(params);
}
else {
final Console console = BerichtsheftShell.getConsole(true);
if (console != null)
BerichtsheftShell.consoleWait(console, true);
Task<Document> task = new Task<Document>(null,
new Job<Document>() {
public void perform(Document doc, Object[] params) throws Exception {
evaluate(doc, popup);
if (followUp != null)
followUp.apply(params);
}
}, params)
{
@Override
protected Document doInBackground() {
try {
setProgress(1);
return parseSite(location, period);
} finally {
setProgress(100);
}
}
};
task.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if ("progress" == evt.getPropertyName()) {
if (console != null) {
BerichtsheftShell.setBerichtsheftShell(console);
int progress = (Integer) evt.getNewValue();
BerichtsheftShell.consoleWait(console, progress < 100);
}
}
}
});
task.execute();
}
}
private Document doc = null;
private ValMap summary(boolean popup, String title) {
final ValMap summary = vmap();
View view = BerichtsheftApp.getJEditView();
try {
final Object out = popup ? new StringWriter() : "";
waiting(view, new ComponentFunction<Void>() {
public Void apply(Component comp, Object[] parms) {
Elements elements = doc.select("pre:contains(D | h |)");
if (elements.size() > 0) {
String header = elements.get(0).text();
println(out, header);
MatchResult[] vbars = findAllIn(header, Pattern.compile("\\|"));
int[] columnPos = new int[vbars.length + 2];
columnPos[0] = -1;
for (int i = 0; i <= vbars.length; i++) {
columnPos[i+1] = i < vbars.length ? vbars[i].start() : header.length();
}
elements = doc.select(".dat");
if (elements.size() > 0) {
for (Element element : elements) {
String row = element.text();
println(out, row);
for (int i = 0; i < columnPos.length - 1; i++) {
String columnName = header.substring(columnPos[i] + 1, columnPos[i+1]).trim();
ValList column = summary.getList(columnName);
int startPos = Math.min(columnPos[i] + 1, row.length());
int endPos = i == columnPos.length - 2 ?
row.length() :
Math.min(columnPos[i+1] + 1, row.length());
String value = row.substring(Math.max(0, startPos), endPos);
column.add(value.trim());
}
}
}
}
return null;
}
});
if (popup) {
JTextArea textArea = new JTextArea();
textArea.setFont(monoSpaced());
textArea.setText(((StringWriter)out).toString());
Component component = new JScrollPane(textArea);
component.setPreferredSize(new Dimension(800,400));
int result = new AlertDialog(view,
getProperty("datadock.weather.title"),
title,
component,
JOptionPane.OK_CANCEL_OPTION,
Behavior.MODAL,
loadIcon("datadock.weather.icon"),
null).open().getResult();
if (result != JOptionPane.OK_OPTION)
return null;
}
} catch (Exception e) {
Log.e(TAG, "summary", e);
}
// println(com.applang.Util2.toString(summary));
return summary;
}
public AlertDialog feedableDialog(String title) {
AlertDialog dialog;
TextView tv = new TextView(null, null);
tv.getTaggedComponent().setFont(monoSpaced());
dialog = new AlertDialog.Builder(BerichtsheftActivity.getInstance(), false)
.setTitle(title)
.setView(tv)
.setNeutralButton(android.R.string.close,
new OnClickListener() {
public void onClick(DialogInterface dialog,
int which) {
dialog.cancel();
}
})
.create();
dialog.setModalExclusionType(AlertDialog.ModalExclusionType.APPLICATION_EXCLUDE);
dialog.open(new Dimension(800, 400));
return dialog;
// feedableDialog(title).feed(1);
}
/*
SKY COVER weight
0/8 CLR-CLEAR 0
1/8 TO 4/8 SCT-SCATTERED 3
5/8 TO 7/8 BKN-BROKEN 6
8/8 OVC-OVERCAST 9
OBS-OBSCURED 10
POB-PARTIAL OBSCURATION
avg
sonnig : 0-2
leicht bewölkt : 2-5
aufgelockert : 5-8
stark bewölkt : 8-10
*/
int weight(Object cc, Object we) {
int skc = -1;
MatchResult mr = findFirstIn(stringValueOf(cc), Pattern.compile("(\\d+)\\/8"));
if (mr != null)
skc = Integer.parseInt(mr.group(1));
switch (skc) {
case 0:
return 0;
case 1: case 2: case 3: case 4:
return 3;
case 5: case 6: case 7:
return 6;
case 8:
return 9;
default:
if ("rain".equals(we))
return 10;
else
return -1;
}
}
String averaged(ValList list) {
int n = 0;
float avg = 0;
for (Object val : list)
if (-1 < (Integer)val) {
avg += (Integer)val;
n++;
}
if (n < 1)
return "";
avg /= n;
if (avg < 2)
return "sonnig";
else if (avg < 5)
return "leicht bewölkt";
else if (avg < 8)
return "aufgelockert";
else
return "stark bewölkt";
}
public static Pattern PRECIPITATION_PATTERN = clippingPattern("\\(", "\\)");
private void evaluateSummary(ValMap summary) {
if (summary == null)
return;
ValMap vormittag = vmap(), nachmittag = vmap();
BidiMultiMap precip = bmap(3);
ArrayList<Double> temps = alist();
ValList list = summary.getList("D");
if (notAvailable(0, list))
return;
String old = list.get(0).toString(), day = old;
for (int i = 0; i < list.size(); i++) {
String hour = summary.getListValue("h", i).toString();
Long instant = pointInTime(day, stripUnits(hour));
if ("06Z".compareTo(hour) <= 0 && "19Z".compareTo(hour) >= 0) {
int weight = weight(
summary.getListValue("CC", i),
summary.getListValue("WE", i));
if ("12Z".compareTo(hour) < 0)
nachmittag.getList(day).add(weight);
else
vormittag.getList(day).add(weight);
}
Object oT = summary.getListValue("T", i);
oT = stripUnits(oT.toString());
temps.add(toDouble(Double.NaN, oT.toString()));
Object oPR = summary.getListValue("PR", i);
if (notNullOrEmpty(oPR)) {
MatchResult[] mr = findAllIn(oPR.toString(), PRECIPITATION_PATTERN);
if (mr.length > 0) {
Integer hours = toInt(0, stripUnits(mr[0].group(2)));
precip.add(instant,
hoursFromDate(instant, -hours),
toDouble(0.1, mr[0].group(1)));
}
}
if (i < list.size() - 1)
day = summary.getListValue("D", i + 1).toString();
else
day = "";
if (!old.equals(day)) {
int oldDay = toInt(-1, old);
Long time = DatePicker.Period.getMillis(oldDay);
if (time != null) {
String description = "";
String vm = averaged(vormittag.getList(old));
if (notNullOrEmpty(vm))
description += String.format("v.m. %s ", vm);
String nm = averaged(nachmittag.getList(old));
if (notNullOrEmpty(nm))
description += String.format("n.m. %s ", nm);
ValMap values = vmap();
values.put(Weathers.DESCRIPTION, description);
values.put(Weathers.PRECIPITATION, interpolate(old, precip));
values.put(Weathers.MAXTEMP, Collections.max(temps));
values.put(Weathers.MINTEMP, Collections.min(temps));
if (null == updateOrInsert(location, time, values))
break;
}
old = day;
temps = alist();
}
}
// for (String d : vormittag.keySet()) {
// list = vormittag.getList(d);
// println("%s vormittag", d, list, averaged(list));
// }
// for (String d : vormittag.keySet()) {
// list = nachmittag.getList(d);
// println("%s nachmittag", d, list, averaged(list));
// }
}
private double interpolate(String day, BidiMultiMap precip) {
double value = 0.0;
long[] interval = intervalInTime(day);
timeLine = precip.getKeys().toArray(new Long[0]);
// println("timeLine", formatDates(timeLine));
// println("interval", formatDates(interval));
int[] index = ints(
index(interval[0]), index(interval[1])
);
long[] old = null;
for (int i = index[1]; i < index[0]; i++) {
ValList rec = precip.get(i);
long[] ival = interval(rec);
double precipitation = (Double)rec.get(2);
double contrib = contribution(interval, ival, precipitation);
double isect = old == null ? 0.0 :
intersection(ival, old);
double portion = (1.0 - isect) * contrib;
// println(formatDates(ival), precipitation, contrib, portion);
value += portion;
old = ival;
}
// println("precipitation on %s : %f", formatDate(epoch), value);
return value;
}
private double contribution(long[] interval, long[] ival, double value) {
long upper = Math.min(interval[1], ival[1]);
long lower = Math.max(interval[0], ival[0]);
double portion = 1.0 * (upper - lower) / (ival[1] - ival[0]);
return portion * value;
}
private long[] interval(ValList rec) {
return new long[]{
(Long)rec.get(1), (Long)rec.get(0)
};
}
private double intersection(long[] ival1, long[] ival2) {
if (ival2[0] >= ival1[1] || ival2[1] <= ival1[0])
return 0.0;
else if (ival2[1] >= ival1[1] && ival2[0] <= ival1[0])
return 1.0;
else if (ival1[1] >= ival2[1] && ival1[0] <= ival2[0])
return 1.0;
else if (ival2[1] >= ival1[1])
return 1.0 * (ival1[1] - ival2[0]) / (ival2[1] - ival2[0]);
else
return 1.0 * (ival2[1] - ival1[0]) / (ival1[1] - ival1[0]);
}
private int index(long epoch) {
return pointer(criterion(epoch));
}
Long[] timeLine;
private int criterion(long epoch) {
return Arrays.binarySearch(timeLine, epoch, new Comparator<Long>() {
@Override
public int compare(Long l1, Long l2) {
if (l1 < l2)
return 1;
else if (l1 > l2)
return -1;
else
return 0;
}
});
}
private int pointer(int crit) {
if (crit < 0)
crit = -crit - 1;
return crit;
}
private long[] intervalInTime(String day) {
Long epoch = DatePicker.Period.getMillis(toInt(-1, day));
if (epoch == null)
return null;
else
return dayInterval(epoch, 1);
}
private Long pointInTime(String day, String hour) {
return DatePicker.Period.getMillisByHour(toInt(-1, day), toInt(0, hour));
}
void measurements(final String marker) {
waiting(null, new ComponentFunction<Void>() {
public Void apply(Component comp, Object[] parms) {
storeValues(tableAfter(marker, 1));
storeValues(tableAfter(marker, 2));
return null;
}
});
}
Element tableAfter(String text, int follower) {
Element table = null, el = null;
Elements tables = doc.select("table:contains(" + text + ")");
int length = Integer.MAX_VALUE;
for (Element t : tables)
if (length > t.html().length()) {
length = t.html().length();
el = t;
}
if (el != null) {
while (el != el.lastElementSibling()) {
el = el.nextElementSibling();
tables = el.select("table");
if (tables.size() > 0) {
follower--;
if (follower > 0)
continue;
table = tables.first();
break;
}
}
}
return table;
}
void storeValues(Element table) {
ValMap values = vmap();
Elements column = table.select("td:eq(0)");
boolean precipitation = column.get(1).text().toLowerCase().startsWith("prec");
Element row = table.select("tr").first();
for (int i = 1; i < row.select("td").size(); i++) {
column = table.select(String.format("td:eq(%d)", i));
int day = Integer.parseInt(column.first().text());
if (precipitation)
values.put("precipitation", toFloat(Float.NaN, column.get(1).text()));
else {
values.put("maxtemp", toFloat(Float.NaN, column.get(1).text()));
values.put("mintemp", toFloat(Float.NaN, column.get(2).text()));
}
long time = DatePicker.Period.getMillis(day);
if (null == updateOrInsert(location, time, values))
break;
}
}
private void evaluate(Document doc, boolean popup) {
this.doc = doc;
count = ints(0,0);
switch (DatePicker.Period.length) {
case DatePicker.Period.MONTH:
measurements("Daily extreme temperatures");
measurements("24 hours rainfall");
break;
default:
evaluateSummary(summary(popup, DatePicker.Period.description()));
break;
}
if (count[0] > 0 || count[1] > 0)
BerichtsheftPlugin.consoleMessage("dataview.updateOrInsert.message",
count[1], count[0]);
}
@Override
public boolean openConnection(String dbPath, Object... params) {
try {
if (super.openConnection(dbPath, arrayextend(params, true, "weathers")))
return true;
getStmt().execute("CREATE TABLE weathers (" +
"_id INTEGER PRIMARY KEY," +
"description TEXT," +
"location TEXT," +
"precipitation FLOAT," +
"maxtemp FLOAT," +
"mintemp FLOAT," +
"created INTEGER," +
"modified INTEGER)");
return true;
} catch (Exception e) {
handleException(e);
return false;
}
}
public int update(long id, ValMap values) throws Exception {
String sql = "update weathers set";
for (Map.Entry<String,Object> entry : values.entrySet())
sql += " " + entry.getKey() + "='" + entry.getValue().toString() + "',";
PreparedStatement ps = getCon().prepareStatement(sql + " modified = ? where _id = ?");
ps.setLong(1, now());
ps.setLong(2, id);
return ps.executeUpdate();
}
public int insert(long id, ValMap values, String location, long time) throws Exception {
String keys = "", vals = "";
for (String key : values.keySet())
keys += "," + key;
for (Object value : values.values())
vals += ",'" + value.toString() + "'";
String sql = String.format(
"insert into weathers (_id%s,location,created,modified) VALUES (?%s,?,?,?)",
keys, vals);
PreparedStatement ps = getCon().prepareStatement(sql);
ps.setLong(1, id);
ps.setString(2, location);
ps.setLong(3, time);
ps.setLong(4, now());
return ps.executeUpdate();
}
public long newId() throws Exception {
ResultSet rs = getStmt().executeQuery("select max(_id) from weathers");
long id = rs.next() ? rs.getLong(1) : -1;
rs.close();
return 1 + id;
}
public long getIdOfDay(String location, long time) throws SQLException {
long[] interval = dayInterval(time, 1);
PreparedStatement ps = getCon().prepareStatement(
"select _id from weathers " +
"where created between ? and ? and location=? " +
"order by created, location");
ps.setLong(1, interval[0]);
ps.setLong(2, interval[1] - 1);
ps.setString(3, location);
ResultSet rs = ps.executeQuery();
long id = -1;
if (rs.next())
id = rs.getLong(1);
rs.close();
return id;
}
private int[] count = new int[2];
public Object updateOrInsert(String location, long time, ValMap values) {
if (getCon() == null) {
message("database connection not open");
BerichtsheftPlugin.print(location,
formatDate(time, DatePicker.calendarFormat, Locale.getDefault()),
values,
NEWLINE);
return -1;
}
try {
long id = getIdOfDay(location, time);
if (id > -1) {
update(id, values);
count[0]++;
}
else {
id = newId();
insert(id, values, location, time);
count[1]++;
}
return id;
} catch (Exception e) {
handleException(e);
return -1;
}
}
}