private static void drawPositions(
     SvgFile svg, UserData user, String stroke, float strokeWidth, boolean showPoints) {
   Position lp = null;
   Position lastGoodp = null;
   for (Position p : user.getPositions()) {
     if (lp != null && !lp.isTruncated() && !p.isTruncated()) {
       svg.line(
           scaleX(lp.getX()),
           scaleY(lp.getY()),
           scaleX(p.getX()),
           scaleY(p.getY()),
           stroke,
           strokeWidth);
     } else if (lastGoodp != null && !p.isTruncated()) {
       svg.line(
           scaleX(lastGoodp.getX()),
           scaleY(lastGoodp.getY()),
           scaleX(p.getX()),
           scaleY(p.getY()),
           stroke,
           strokeWidth,
           "stroke-dasharray=\"" + MAG * 3 + " " + MAG * 3 + "\"");
       lastGoodp = null;
     }
     lp = p;
     if (!p.isTruncated()) lastGoodp = p;
     if (showPoints) svg.circle(scaleX(p.getX()), scaleY(p.getY()), 3, stroke, 0.4f, null);
   }
 }
 /** colour by hour of day */
 private static void drawPositionsHod(SvgFile svg, List<UserData> users, boolean showPoints) {
   float strokeWidth = 0.5f;
   for (UserData user : users) {
     Position lp = null;
     Position lastGoodp = null;
     for (Position p : user.getPositions()) {
       String stroke = getColour(scaleHod(p.getHourOfDay()));
       if (lp != null && !lp.isTruncated() && !p.isTruncated()) {
         svg.line(
             scaleX(lp.getX()),
             scaleY(lp.getY()),
             scaleX(p.getX()),
             scaleY(p.getY()),
             stroke,
             strokeWidth);
       } else if (lastGoodp != null && !p.isTruncated()) {
         svg.line(
             scaleX(lastGoodp.getX()),
             scaleY(lastGoodp.getY()),
             scaleX(p.getX()),
             scaleY(p.getY()),
             stroke,
             strokeWidth,
             "stroke-dasharray=\"" + MAG * 3 + " " + MAG * 3 + "\"");
         lastGoodp = null;
       }
       lp = p;
       if (!p.isTruncated()) lastGoodp = p;
       if (showPoints) svg.circle(scaleX(p.getX()), scaleY(p.getY()), 3, stroke, 0.4f, null);
     }
   }
 }
 private static Timeline getTimeline(UserData user) {
   Timeline tl = new Timeline();
   TimelinePoint lasttp = null;
   boolean truncated = false;
   for (Position p : user.getPositions()) {
     TimelinePoint tp = new TimelinePoint(p.getHourOfDay(), scaleX(p.getX()), scaleY(p.getY()));
     if (lasttp == null) lasttp = tp;
     else {
       if (!p.isTruncated()) {
         TimelineSegment s1 =
             new TimelineSegment(
                 lasttp,
                 tp,
                 (truncated || p.isTruncated() ? DrawTimelines.DASHED : DrawTimelines.SOLID));
         tl.getSegments().add(s1);
         lasttp = tp;
         truncated = p.isTruncated();
       } else truncated = true;
     }
   }
   return tl;
 }
  /** @param args */
  public static void main(String[] args) {
    if (args.length != 3) {
      logger.log(Level.SEVERE, "usage: <logdir> <zonefile> <outdir>");
      System.exit(-1);
    }
    try {
      cLat = 52.987253;
      cLon = -1.8895595;
      cX = Mercator.mercX(cLon);
      cY = Mercator.mercY(cLat);
      // http://maps.googleapis.com/maps/api/staticmap?center=40.714728,-73.998672&zoom=12&size=400x400&maptype=satellite&sensor=false
      // INFO: lon: -1.896922 to -1.882197, lat: 52.984308 to 52.990198
      // http://maps.googleapis.com/maps/api/staticmap?center=52.987253,-1.8895595&zoom=14&size=640x640&maptype=satellite&sensor=false
      List<UserData> users = LogReader.readLogs(new File(args[0]));
      Map<Integer, Zone> zones = LogReader.readZones(new File(args[1]));
      logger.log(Level.INFO, "Read " + zones.size() + " zones");
      File outdir = new File(args[2]);
      if (!outdir.isDirectory()) {
        logger.log(Level.SEVERE, "Output directory not valid: " + outdir);
        System.exit(-1);
      }

      width = 2000;

      SvgFile svg = createBackgroundFile(new File(outdir, "park.svg"));
      {
        double minX = Double.MAX_VALUE,
            minY = Double.MAX_VALUE,
            maxX = -Double.MAX_VALUE,
            maxY = -Double.MAX_VALUE;
        double minLat = Double.MAX_VALUE,
            minLon = Double.MAX_VALUE,
            maxLat = -Double.MAX_VALUE,
            maxLon = -Double.MAX_VALUE;
        float minHOD = Float.MAX_VALUE, maxHOD = -Float.MAX_VALUE;
        float minHOD2 = Float.MAX_VALUE, maxHOD2 = -Float.MAX_VALUE;
        for (UserData user : users) {
          for (Position p : user.getPositions()) {
            if (p.isTruncated()) continue;

            double x = p.getX();
            double y = p.getY();
            if (x < minX) minX = x;
            if (x > maxX) maxX = x;
            if (y < minY) minY = y;
            if (y > maxY) maxY = y;
            double lat = p.getLat();
            double lon = p.getLon();
            if (lat < minLat) minLat = lat;
            if (lat > maxLat) maxLat = lat;
            if (lon < minLon) minLon = lon;
            if (lon > maxLon) maxLon = lon;
            float hod = p.getHourOfDay();
            if (hod < minHOD) minHOD = hod;
            if (hod > maxHOD) maxHOD = hod;
          }
          for (Event e : user.getEvents()) {
            Float hod = e.getHourOfDay();
            if (hod == null) continue;
            if (hod < minHOD2) minHOD2 = hod;
            if (hod > maxHOD2) maxHOD2 = hod;
          }
        }
        logger.log(
            Level.INFO, "Ranges, x: " + minX + " to " + maxX + ", y: " + minY + " to " + maxY);
        logger.log(
            Level.INFO,
            "Ranges, lon: " + minLon + " to " + maxLon + ", lat: " + minLat + " to " + maxLat);
        logger.log(Level.INFO, "Ranges, HOD: " + minHOD + " to " + maxHOD);
        logger.log(Level.INFO, "Ranges, HOD2: " + minHOD2 + " to " + maxHOD2);
        minX -= BORDER_M;
        minY -= BORDER_M;
        maxX += BORDER_M;
        maxY += BORDER_M;
      }
      // String stroke = "#ff0000";
      drawPositions(svg, users, "#222", false);
      drawZones(svg, zones);

      svg.close();
      svg = createBackgroundFile(new File(outdir, "allusers.svg"));
      drawPositions(svg, users, null, false);
      svg.close();

      svg = createBackgroundFile(new File(outdir, "allevents.svg"));
      drawPositions(svg, users, null, false);
      drawEvents(svg, users, null, zones);
      svg.close();

      svg.close();
      svg = createBackgroundFile(new File(outdir, "allusershod.svg"));
      drawPositionsHod(svg, users, false);
      drawEventsHod(svg, users, zones);
      svg.close();

      svg = createBackgroundFile(new File(outdir, "allusershodtl.svg"));
      drawZones2(svg, zones);
      drawPositionsHod(svg, users, false);
      drawTimelineEventsHod(svg, users, zones);
      svg.close();

      Set<String> groups = new HashSet<String>();
      for (UserData u : users) {
        groups.add(u.getTrialid());
      }
      for (String group : groups) {
        logger.info("Group " + group);
        svg = createBackgroundFile(new File(outdir, "allusershod-" + group + ".svg"));
        List<UserData> members = new LinkedList<UserData>();
        for (UserData u : users) if (u.getTrialid().equals(group)) members.add(u);
        drawPositionsHod(svg, members, false);
        drawEventsHod(svg, members, zones);
        svg.close();

        svg = createBackgroundFile(new File(outdir, "allusershodtl-" + group + ".svg"));
        drawZones2(svg, zones);
        drawPositionsHod(svg, members, false);
        drawTimelineEventsHod(svg, members, zones);
        svg.close();
      }
      for (UserData user : users) {
        logger.info("User " + user.getTrialid() + " " + user.getTrialuserid());
        svg =
            createBackgroundFile(
                new File(
                    outdir,
                    "allusershod-" + user.getTrialid() + "-" + user.getTrialuserid() + ".svg"));
        List<UserData> members = new LinkedList<UserData>();
        members.add(user);
        drawPositionsHod(svg, members, false);
        drawEventsHod(svg, members, zones);
        svg.close();

        svg =
            createBackgroundFile(
                new File(
                    outdir,
                    "allusershodtl-" + user.getTrialid() + "-" + user.getTrialuserid() + ".svg"));
        drawZones2(svg, zones);
        drawPositionsHod(svg, members, false);
        drawTimelineEventsHod(svg, members, zones);
        svg.close();
      }

    } catch (Exception e) {
      logger.log(Level.SEVERE, "Error creating " + args[1], e);
    }
  }