如何在 Processing 中点对点画线

How to draw a line from point to point in Processing

我正在尝试生成一个图形,用一条线从起点到目的地绘制货运路径。我已将纬度和经度数据转换为适合美国地图的像素 (1620, 1080)。

当前的书写方式是按照它们在我的 csv 文件中的排序方式顺序绘制的。但是,我希望这些线从起点辐射到终点。现在我只能想办法把已经画好的线放下来。

我认为代码的相关部分在 // Draw Lines

long current;

int x;
int y;
ArrayList loads;

void setup() {
  size(1620, 1080);
  background(55);
  smooth();
  frameRate(15);


// Draw US Map



String[] lines = loadStrings("Map2.csv");    // File containing coordinates to plot US Map
  stroke(55);
  strokeWeight(1);
  smooth();

  String[] pieces = split(lines[0], ',');

  for ( int i = 0; i < lines.length; i++) {

    fill(0);

    beginShape();
    current = int(pieces[0]);

    while ( current == int(pieces[0]) & i < lines.length) {

      x = int(pieces[2]);
      y = int(pieces[1]);
      vertex(x, y);
      i++;

      if ( i < lines.length) {
        pieces = split(lines[i], ',');
      }
    }
    endShape();
  }



// Add Lakes to Map




 String[] lines2 = loadStrings("Water.csv");    // File containing coordinates to plot great lakes
  smooth();

  fill(22, 25, 180);

  String[] pieces2 = split(lines2[0], ',');
  for (int i = 0; i < lines2.length; i++)
  {

    fill(110);
    beginShape();
    current =  int(pieces2[0]);

    while (current == int(pieces2[0]) & i < lines2.length) {

      x = int(pieces2[2]);
      y = int(pieces2[1]);
      vertex(x, y);
      i++;
      if (i < lines2.length) {
        pieces2 = split(lines2[i], ',');
      }
    }
    endShape();
  }


// Draw Lines



 loads = new ArrayList();

  String[] loadset = loadStrings("data1.csv");    
  for ( int i3 = 0; i3 < loadset.length; i3++) {
    String[] loads2 = split(loadset[i3], ',');
    loads.add( new Lane(int(loads2[0]), int(loads2[1]), int(loads2[2]), int(loads2[3])) );
  }
  }
     int i=1;
       int imax = 1;
       int incmult = 1;

    void draw() {
     if (i < loads.size()-imax){

      for(int iadd = 0; iadd < imax; iadd++)
      {
            Lane Lane = (Lane) loads.get(iadd);
            Lane.display();
            Lane = (Lane) loads.get(i+iadd);
            Lane.display();      
      }
          i +=imax;  
     }
      imax = imax + incmult;
    }

    class Lane {
        int x;
        int y;
        int x2;
        int y2;

        Lane( int tempX, int tempY, int tempX2, int tempY2) {
          x  = tempX;
          y  = tempY;
          x2 = tempX2;
          y2 = tempY2;
        }

        void display() {
          int r = 65;
          int g = 255;
          int b = 35;
          strokeWeight(1);
          stroke(r, g, b, 55);
          line(x, y, x2, y2);

          stroke(255, 255, 255);      // Origin
          fill(255, 255, 255, 55);
          ellipse(x, y, 3, 3);

          stroke(171, 62, 193);       // Destination
          fill(171, 62, 193);
          ellipse(x2, y2, 3, 3);
      }
      }

我的 data1.csv 文件包含四列 x, y, x2, y2,其中 (x, y) 表示原点,(x2, y2) 表示目标坐标。

//  data.csv 

data[0] data[1] data[2] data[3]
929     327      602      507
1335    458      1327     782
1422    325      848      744
1302    280      1118     458
1041    583      1193     666
1267    616      1058     394
1215    671      1351     857
1334    851      1410     946
1334    851      1409     916
828     761      861      653
1386    323      1203     594
1037    293      1013     522
908     869      958      532
1029    331      1053     409
906     357      828      761
.        .       .        .
.        .       .        .

更新 我已经添加了指向我的数据的链接,因为我仍然无法按照我的设想绘制图像。

地图2

Water

数据1 <"https://docs.google.com/spreadsheets/d/1QzbCGW8H6PZgLkmWN8OyplVNTJhp3tlPGxR_Zv6lttM/pub?output=csv">

目前问题还不是很清楚,所以当前形式的回答不完整。

以下是一些可以简化代码并希望有助于实现最终目标的建议:

  1. 使用 loadTable(). It supports the tab support and headers as your sample data. The nice thing about parsing the headers is that you can use the column label to parse each value using functions readily available functions like getInt()
  2. 尝试 Processing 的内置 CSV 解析器
  3. 尝试使用 PShape:您可以在设置中设置一次绘制命令,然后在一个命令中渲染最终形状。优点是您可以在需要时访问以后使用的形状和顶点。

这是一个使用此 TSV 的基本示例,该示例基于以上保存为 data.tsv

的数据
Table data;
PShape dataPlot;

size(1620, 1080,P2D);
//create a group to store the lines from each row
dataPlot = createShape();
//load the data, specifying it has a header and it's tab separated
data = loadTable("data.tsv", "header, tsv");
//traverse each row
dataPlot.beginShape(LINES);
for(TableRow row : data.rows()){
  //extract each value
  int x1 = row.getInt("x1");
  int y1 = row.getInt("y1");
  int x2 = row.getInt("x2");
  int y2 = row.getInt("y2");
  //add the coordinates as lines to the group
  dataPlot.stroke(160);
  dataPlot.vertex(x1,y1);
  dataPlot.stroke(0);
  dataPlot.vertex(x2,y2);
}
dataPlot.endShape();
//render the plot
shape(dataPlot); 

使用深灰色到浅灰色显示路径的起点和终点,这是使用部分示例数据的结果:

如果您需要在创建 PShape 实例后访问每个顶点,您可以使用 getVertex()。它需要您可能想要检索的顶点的索引。 例如,dataPlot.getVertex(0);dataPlot.getVertex(1); 将 return 第一行的坐标,dataPlot.getVertex(2);dataPlot.getVertex(2); 将 return 第二行的坐标等等。

如果管理树状层次结构而不是顶点索引更容易,您可以创建 PShape 组。唯一需要注意的是,创建的以下子 PShape 实例将使用以 set 为前缀的函数绘制,而不是典型的 stroke()/fill()/等。你已经习惯了:setStroke()/setFill()/etc.

下面是一个使用 PShape 组并将距离映射到线条颜色和粗细的代码示例:

Table data;
PShape dataPlot;

size(1620, 1080, P2D);
//create a group to store the lines from each row
dataPlot = createShape(GROUP);
//load the data, specifying it has a header and it's tab separated
data = loadTable("data.tsv", "header, tsv");
//traverse each row
for (TableRow row : data.rows ()) {
  //extract each value
  int x1 = row.getInt("x1");
  int y1 = row.getInt("y1");
  int x2 = row.getInt("x2");
  int y2 = row.getInt("y2");
  //add the coordinates as lines to the group
  PShape line = createShape(LINE, x1, y1, x2, y2);
  float dist = dist(x1, y1, x2, y2);
  line.setStroke(color(map(dist, 0, height, 160, 0)));
  line.setStrokeWeight(map(dist, 0, height, 10.0, 1.0));
  dataPlot.addChild(line);
}
//render the plot
shape(dataPlot); 

在这种情况下,检索第一行将如下所示:

PShape line0 = dataPlot.getChild(0);

您允许访问它的两个顶点:

println(line0.getVertex(0));
println(line0.getVertex(1));

如果要为这些位置设置动画,可以轻松使用 lerp() on single values or PVector's lerp() 方法(对于 PVectors :))。此函数需要一对 start/end 值和一个标准化值(介于 0.0 和 1.0 之间)和 return 之间的值。将其视为沿途的百分比:

  • 传递 0.0 将成为该行的开始
  • 通过0.5将是沿线的50%
  • 传递 1.0 将是行尾

这是一个在遍历每条线时绘制椭圆的基本示例,它的颜色指示开始(黑色)或结束(白色)位置(拖动应该允许手动控制将 X 轴映射到线遍历):

Table data;
PShape plot;

void setup(){
  size(1620, 1080, P2D);
  //create a group to store the lines from each row
  plot = createShape(GROUP);
  //load the data, specifying it has a header and it's tab separated
  data = loadTable("data.tsv", "header, tsv");
  //traverse each row
  for (TableRow row : data.rows ()) {
    //extract each value
    int x1 = row.getInt("x1");
    int y1 = row.getInt("y1");
    int x2 = row.getInt("x2");
    int y2 = row.getInt("y2");
    //add the coordinates as lines to the group
    PShape line = createShape(LINE, x1, y1, x2, y2);
    float dist = dist(x1, y1, x2, y2);
    line.setStroke(color(map(dist, 0, height, 160, 0)));
    line.setStrokeWeight(map(dist, 0, height, 10.0, 1.0));
    plot.addChild(line);
  }
}
void draw(){
  background(255);
  //render the plot
  shape(plot);
  //animate the trajectories
  //use normalized (between 0.0 and 1.0) value to traverse the paths (think of it as 0 and 100%, 0 is at the start 100% is at the end)
  //if can be interactive
  float traversal;
  if(mousePressed) {
    traversal = map(mouseX,0,width,0.0,1.0);
  }else{//or time based, up to you :)
    traversal = map(sin(frameCount * 0.1),-1.0,1.0,0.0,1.0);
  } 
  //for each trajectory
  for(int i = 0 ; i < plot.getChildCount(); i++){
    PShape line = plot.getChild(i);
    //access each line's start and end points
    PVector start = line.getVertex(0);
    PVector end   = line.getVertex(1);
    //calculate the linearly interpolated point in between start end using the traversal value and lerp()
    PVector inbetween = PVector.lerp(start,end,traversal);
    //use the interpolated value to draw
    fill(traversal * 255);
    ellipse(inbetween.x,inbetween.y,15,15);
  }
}

这是一个非常相似的例子,只淡化点:

Table data;
PShape plot;

void setup(){
  size(1620, 1080, P2D);
  //create a group to store the lines from each row
  plot = createShape(GROUP);
  //load the data, specifying it has a header and it's tab separated
  data = loadTable("data.tsv", "header, tsv");
  //traverse each row
  for (TableRow row : data.rows ()) {
    //extract each value
    int x1 = row.getInt("x1");
    int y1 = row.getInt("y1");
    int x2 = row.getInt("x2");
    int y2 = row.getInt("y2");
    //add the coordinates as lines to the group
    PShape line = createShape(LINE, x1, y1, x2, y2);
    float dist = dist(x1, y1, x2, y2);
    line.setStroke(color(map(dist, 0, height, 160, 0)));
    line.setStrokeWeight(map(dist, 0, height, 10.0, 1.0));
    plot.addChild(line);
  }
  //clear the background
  noStroke();
  shape(plot);//this needs to be drawn at least once it seems
  background(255);
}
void draw(){
  //hacky fade effect, change the alpha (16) transparency value to experiment with fade amount 
  fill(255,16);
  rect(0,0,width,height);
  //animate the trajectories
  //use normalized (between 0.0 and 1.0) value to traverse the paths (think of it as 0 and 100%, 0 is at the start 100% is at the end)
  //if can be interactive
  float traversal;
  if(mousePressed) {
    traversal = map(mouseX,0,width,0.0,1.0);
  }else{//or time based, up to you :)
    traversal = map(sin(frameCount * 0.01),-1.0,1.0,0.0,1.0);
  } 
  //for each trajectory
  for(int i = 0 ; i < plot.getChildCount(); i++){
    PShape line = plot.getChild(i);
    //access each line's start and end points
    PVector start = line.getVertex(0);
    PVector end   = line.getVertex(1);
    //calculate the linearly interpolated point in between start end using the traversal value and lerp()
    PVector inbetween = PVector.lerp(start,end,traversal);
    //use the interpolated value to draw
    fill(lerpColor(color(255,0,0),color(0,255,0),traversal));
    ellipse(inbetween.x,inbetween.y,15,15);
  }
}

这是另一个有趣的小变化:

Table data;
PShape plot;

void setup(){
  size(1620, 1080, P2D);
  smooth(8);
  //create a group to store the lines from each row
  plot = createShape(GROUP);
  //load the data, specifying it has a header and it's tab separated
  data = loadTable("data.tsv", "header, tsv");
  //traverse each row
  for (TableRow row : data.rows ()) {
    //extract each value
    int x1 = row.getInt("x1");
    int y1 = row.getInt("y1");
    int x2 = row.getInt("x2");
    int y2 = row.getInt("y2");
    //add the coordinates as lines to the group
    PShape line = createShape(LINE, x1, y1, x2, y2);
    plot.addChild(line);
  }
  shape(plot);
  strokeWeight(5.0);
}
void draw(){
  //hacky fade effect, change the alpha/transparency value to experiment with fade amount 
  background(255);
  //animate the trajectories
  //use normalized (between 0.0 and 1.0) value to traverse the paths (think of it as 0 and 100%, 0 is at the start 100% is at the end)
  //if can be interactive
  float traversal;
  if(mousePressed) {
    traversal = map(mouseX,0,width,0.0,1.0);
  }else{//or time based, up to you :)
    traversal = map(sin(frameCount * 0.01),-1.0,1.0,0.0,1.0);
  } 
  beginShape(LINES);
  //for each trajectory
  for(int i = 0 ; i < plot.getChildCount(); i++){
    PShape line = plot.getChild(i);
    //access each line's start and end points
    PVector start = line.getVertex(0);
    PVector end   = line.getVertex(1);
    //calculate the linearly interpolated point in between start end using the traversal value and lerp()
    PVector inbetween = PVector.lerp(start,end,traversal);
    //use the interpolated value to draw
    stroke(64);
    vertex(start.x,start.y);
    stroke(160);
    vertex(inbetween.x,inbetween.y);
  }
  endShape();
}

视频演示 here

如果线性插值不够,并且您需要更多地控制时间或插值类型,请查看 Benedikt Groß's Ani library